/** * Desploy all the required files into the provided directories * * @param core_kernel_classes_Resource $item * @param string $language * @param tao_models_classes_service_StorageDirectory $publicDirectory * @param tao_models_classes_service_StorageDirectory $privateDirectory * @return common_report_Report */ protected function deployQtiItem(core_kernel_classes_Resource $item, $language, tao_models_classes_service_StorageDirectory $publicDirectory, tao_models_classes_service_StorageDirectory $privateDirectory) { $qtiService = Service::singleton(); // retrieve the media assets try { $qtiItem = $this->retrieveAssets($item, $language, $publicDirectory); //store variable qti elements data into the private directory $variableElements = $qtiService->getVariableElements($qtiItem); $privateDirectory->write($language . DIRECTORY_SEPARATOR . self::VAR_ELT_FILE_NAME, json_encode($variableElements)); //create the item.json file in private directory $itemPacker = new QtiItemPacker(); $itemPacker->setReplaceXinclude(false); $itemPack = $itemPacker->packQtiItem($item, $language, $qtiItem, $publicDirectory); $this->itemJson = $itemPack->JsonSerialize(); //get the filtered data to avoid cheat $data = $qtiItem->getDataForDelivery(); $this->itemJson['data'] = $data['core']; $privateDirectory->write($language . DIRECTORY_SEPARATOR . self::ITEM_FILE_NAME, json_encode($this->itemJson)); return new common_report_Report(common_report_Report::TYPE_SUCCESS, __('Successfully compiled "%s"', $language)); } catch (\tao_models_classes_FileNotFoundException $e) { return new common_report_Report(common_report_Report::TYPE_ERROR, __('Unable to retrieve asset "%s"', $e->getFilePath())); } catch (XIncludeException $e) { return new common_report_Report(common_report_Report::TYPE_ERROR, $e->getUserMessage()); } catch (\Exception $e) { return new common_report_Report(common_report_Report::TYPE_ERROR, $e->getMessage()); } }
/** * Resolve the given TAO Item URI in the path to * the related QTI-XML file. * * @param string $url The URI of the TAO Item to resolve. * @return string The path to the related QTI-XML file. * @throws ResolutionException If an error occurs during the resolution of $url. */ public function resolve($url) { $taoItem = new core_kernel_classes_Resource($url); if ($taoItem->exists() === false) { $msg = "The QTI Item with URI '{$url}' cannot be found."; throw new ResolutionException($msg); } // The item is retrieved from the database. // We can try to reach the QTI-XML file by detecting // where it is supposed to be located. // strip xinclude, we don't need that at the moment. $raw = $this->service->getXmlByRdfItem($this->getResource($url)); $tmpfile = sys_get_temp_dir() . '/' . md5($url) . '.xml'; $raw = preg_replace("/<xi:include(?:.*)>/u", '', $raw); file_put_contents($tmpfile, $raw); return $tmpfile; }
public static function tearDownAfterClass() { \taoItems_models_classes_ItemsService::singleton()->deleteItem(self::$itemResource); // Unegister Metadata Injector. \oat\taoQtiItem\model\qti\Service::singleton()->getMetadataRegistry()->unregisterMetadataInjector('oat\\taoQtiItem\\model\\qti\\metadata\\ontology\\LomInjector'); // Unregister Metadata Extractor. \oat\taoQtiItem\model\qti\Service::singleton()->getMetadataRegistry()->unregisterMetadataExtractor('oat\\taoQtiItem\\model\\qti\\metadata\\imsManifest\\ImsManifestMetadataExtractor'); // Unregister Metadata Guardian. \oat\taoQtiItem\model\qti\Service::singleton()->getMetadataRegistry()->unregisterMetadataGuardian('oat\\taoQtiItem\\model\\qti\\metadata\\guardians\\LomIdentifierGuardian'); }
public static function tearDownAfterClass() { \taoItems_models_classes_ItemsService::singleton()->deleteItem(self::$itemResource); // Unregister Metadata ClassLookup. \oat\taoQtiItem\model\qti\Service::singleton()->getMetadataRegistry()->unregisterMetadataClassLookup('oat\\taoQtiItem\\model\\qti\\metadata\\classLookups\\LabelClassLookup'); // Unregister Metadata Extractor. \oat\taoQtiItem\model\qti\Service::singleton()->getMetadataRegistry()->unregisterMetadataExtractor('oat\\taoQtiItem\\model\\qti\\metadata\\imsManifest\\ImsManifestMetadataExtractor'); // Delete fake class $class = new \core_kernel_classes_Class('http://www.test.com#mytestclass'); $class->delete(true); }
/** * render used for deploy and preview * * @access public * @author Joel Bout, <*****@*****.**> * @param core_kernel_classes_Resource $item * @param $langCode * @throws \common_Exception * @return string */ public function render(core_kernel_classes_Resource $item, $langCode) { $returnValue = (string) ''; $qitService = Service::singleton(); $qtiItem = $qitService->getDataItemByRdfItem($item, $langCode); if (!is_null($qtiItem)) { $returnValue = $qitService->renderQTIItem($qtiItem, $langCode); } else { common_Logger::w('No qti data for item ' . $item->getUri() . ' in ' . __FUNCTION__, 'taoQtiItem'); } return (string) $returnValue; }
protected function getAssets(\core_kernel_classes_Resource $item, $lang) { $qtiItem = Service::singleton()->getDataItemByRdfItem($item, $lang); $assetParser = new AssetParser($qtiItem); $assetParser->setGetSharedLibraries(false); $returnValue = array(); 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; }
/** * Build, merge and export the IMS Manifest into the target ZIP archive. * * @throws */ public function exportManifest($options = array()) { $base = $this->buildBasePath(); $zipArchive = $this->getZip(); $qtiFile = ''; $qtiResources = array(); for ($i = 0; $i < $zipArchive->numFiles; $i++) { $fileName = $zipArchive->getNameIndex($i); if (preg_match("@^" . preg_quote($base) . "@", $fileName)) { if (basename($fileName) == 'qti.xml') { $qtiFile = $fileName; } else { $qtiResources[] = $fileName; } } } $qtiItemService = Service::singleton(); //@todo add support of multi language packages $rdfItem = $this->getItem(); $qtiItem = $qtiItemService->getDataItemByRdfItem($rdfItem); if (!is_null($qtiItem)) { // -- Prepare data transfer to the imsmanifest.tpl template. $qtiItemData = array(); // alter identifier for export to avoid any "clash". $qtiItemData['identifier'] = $this->buildIdentifier(); $qtiItemData['filePath'] = $qtiFile; $qtiItemData['medias'] = $qtiResources; $qtiItemData['adaptive'] = $qtiItem->getAttributeValue('adaptive') === 'adaptive' ? true : false; $qtiItemData['timeDependent'] = $qtiItem->getAttributeValue('timeDependent') === 'timeDependent' ? true : false; $qtiItemData['toolName'] = $qtiItem->getAttributeValue('toolVendor'); $qtiItemData['toolVersion'] = $qtiItem->getAttributeValue('toolVersion'); $qtiItemData['interactions'] = array(); foreach ($qtiItem->getInteractions() as $interaction) { $interactionData = array(); $interactionData['type'] = $interaction->getQtiTag(); $qtiItemData['interactions'][] = $interactionData; } // -- Build a brand new IMS Manifest. $newManifest = $this->renderManifest($options, $qtiItemData); if ($this->hasManifest()) { // Merge old manifest and new one. $dom1 = $this->getManifest(); $dom2 = $newManifest; $resourceNodes = $dom2->getElementsByTagName('resource'); $resourcesNodes = $dom1->getElementsByTagName('resources'); foreach ($resourcesNodes as $resourcesNode) { foreach ($resourceNodes as $resourceNode) { $newResourceNode = $dom1->importNode($resourceNode, true); $resourcesNode->appendChild($newResourceNode); } } // rendered manifest is now useless. unset($dom2); } else { // Brand new manifest. $this->setManifest($newManifest); } // -- Overwrite manifest in the current ZIP archive. $zipArchive->addFromString('imsmanifest.xml', $this->getManifest()->saveXML()); } else { $itemLabel = $this->getItem()->getLabel(); throw new common_Exception("the item '{$itemLabel}' involved in the export process has no content."); } }
/** * (non-PHPdoc) * @see taoItems_actions_ItemPreview::getRenderedItem() */ protected function getRenderedItem($item) { //@todo make getRenderedItem language dependent $lang = \common_session_SessionManager::getSession()->getDataLanguage(); $qtiItem = Service::singleton()->getDataItemByRdfItem($item, $lang, true); $contentVariableElements = array_merge($this->getModalFeedbacks($qtiItem), $this->getRubricBlocks($qtiItem)); $taoBaseUrl = common_ext_ExtensionsManager::singleton()->getExtensionById('tao')->getConstant('BASE_WWW'); $qtiBaseUrl = common_ext_ExtensionsManager::singleton()->getExtensionById('taoQtiItem')->getConstant('BASE_WWW'); $taoLibUrl = $taoBaseUrl . 'js/lib/'; $taoQtiItemLibUrl = $qtiBaseUrl . 'js/runtime/'; $xhtml = $qtiItem->toXHTML(array('contentVariableElements' => $contentVariableElements, 'js_var' => array('view' => $this->getRequestView()), 'path' => array('tao' => $taoLibUrl, 'taoQtiItem' => $taoQtiItemLibUrl))); return $xhtml; }
public function getItemData() { $returnValue = array('itemData' => null); if ($this->hasRequestParameter('uri')) { $lang = taoItems_models_classes_ItemsService::singleton()->getSessionLg(); $itemUri = tao_helpers_Uri::decode($this->getRequestParameter('uri')); $itemResource = new core_kernel_classes_Resource($itemUri); $item = Service::singleton()->getDataItemByRdfItem($itemResource, $lang, false); //do not resolve xinclude here, leave it to the client side if (!is_null($item)) { $returnValue['itemData'] = $item->toArray(); } } $this->returnJson($returnValue); }
/** * Build, merge and export the IMS Manifest into the target ZIP archive. * * @throws */ public function exportManifest($options = array()) { $asApip = isset($options['apip']) && $options['apip'] === true; $base = $this->buildBasePath(); $zipArchive = $this->getZip(); $qtiFile = ''; $qtiResources = array(); for ($i = 0; $i < $zipArchive->numFiles; $i++) { $fileName = $zipArchive->getNameIndex($i); if (preg_match("@^" . preg_quote($base) . "@", $fileName)) { if (basename($fileName) == 'qti.xml') { $qtiFile = $fileName; } else { $qtiResources[] = $fileName; } } } $qtiItemService = Service::singleton(); //@todo add support of multi language packages $rdfItem = $this->getItem(); $qtiItem = $qtiItemService->getDataItemByRdfItem($rdfItem); if (!is_null($qtiItem)) { // -- Prepare data transfer to the imsmanifest.tpl template. $qtiItemData = array(); // alter identifier for export to avoid any "clash". $qtiItemData['identifier'] = $this->buildIdentifier(); $qtiItemData['filePath'] = $qtiFile; $qtiItemData['medias'] = $qtiResources; $qtiItemData['adaptive'] = $qtiItem->getAttributeValue('adaptive') === 'adaptive' ? true : false; $qtiItemData['timeDependent'] = $qtiItem->getAttributeValue('timeDependent') === 'timeDependent' ? true : false; $qtiItemData['toolName'] = $qtiItem->getAttributeValue('toolVendor'); $qtiItemData['toolVersion'] = $qtiItem->getAttributeValue('toolVersion'); $qtiItemData['interactions'] = array(); foreach ($qtiItem->getInteractions() as $interaction) { $interactionData = array(); $interactionData['type'] = $interaction->getQtiTag(); $qtiItemData['interactions'][] = $interactionData; } // -- Build a brand new IMS Manifest. $dir = \common_ext_ExtensionsManager::singleton()->getExtensionById('taoQtiItem')->getDir(); $tpl = $asApip === false ? $dir . 'model/qti/templates/imsmanifest.tpl.php' : $dir . 'model/qti/templates/imsmanifestApip.tpl.php'; $templateRenderer = new taoItems_models_classes_TemplateRenderer($tpl, array('qtiItems' => array($qtiItemData), 'manifestIdentifier' => 'MANIFEST-' . tao_helpers_Display::textCleaner(uniqid('tao', true), '-'))); $renderedManifest = $templateRenderer->render(); $newManifest = new DOMDocument('1.0', TAO_DEFAULT_ENCODING); $newManifest->loadXML($renderedManifest); if ($this->hasManifest()) { // Merge old manifest and new one. $dom1 = $this->getManifest(); $dom2 = $newManifest; $dom2->loadXML($renderedManifest); $resourceNodes = $dom2->getElementsByTagName('resource'); $resourcesNodes = $dom1->getElementsByTagName('resources'); foreach ($resourcesNodes as $resourcesNode) { foreach ($resourceNodes as $resourceNode) { $newResourceNode = $dom1->importNode($resourceNode, true); $resourcesNode->appendChild($newResourceNode); } } // rendered manifest is now useless. unset($dom2); } else { // Brand new manifest. $this->setManifest($newManifest); } // -- Overwrite manifest in the current ZIP archive. $zipArchive->addFromString('imsmanifest.xml', $this->getManifest()->saveXML()); } else { $itemLabel = $this->getItem()->getLabel(); throw new common_Exception("the item '{$itemLabel}' involved in the export process has no content."); } }
/** * @param core_kernel_classes_Resource $item * @param string $lang * @return string */ protected function getXmlByItem(core_kernel_classes_Resource $item, $lang = '') { return Service::singleton()->getXmlByRdfItem($item, $lang); }
/** * @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; }
/** * Load Dom & Xpath of xml item content & register xpath namespace * * @param \core_kernel_classes_Resource $item * @return $this * @throws ExtractorException */ private function loadXml(\core_kernel_classes_Resource $item) { $itemService = Service::singleton(); $xml = $itemService->getXmlByRdfItem($item); if (empty($xml)) { throw new ExtractorException('No content found for item ' . $item->getUri()); } $this->dom = new \DOMDocument(); $this->dom->loadXml($xml); $this->xpath = new \DOMXpath($this->dom); $this->xpath->registerNamespace('qti', 'http://www.imsglobal.org/xsd/imsqti_v2p1'); return $this; }
/** * constructor */ public function __construct() { $this->baseDevDir = $this->getBaseDevDir(); $this->baseDevUrl = $this->getBaseDevUrl(); $this->sharedLibRegistry = Service::singleton()->getSharedLibrariesRegistry(); }
/** * Desploy all the required files into the provided directories * * @param core_kernel_classes_Resource $item * @param string $language * @param string $publicDirectory * @param string $privateFolder * @return common_report_Report */ protected function deployQtiItem(core_kernel_classes_Resource $item, $language, $publicDirectory, $privateFolder) { // start debugging here common_Logger::d('destination original ' . $publicDirectory . ' ' . $privateFolder); $itemService = taoItems_models_classes_ItemsService::singleton(); $qtiService = Service::singleton(); //copy item.xml file to private directory $itemFolder = $itemService->getItemFolder($item, $language); tao_helpers_File::copy($itemFolder . 'qti.xml', $privateFolder . 'qti.xml', false); //copy client side resources (javascript loader) $qtiItemDir = \common_ext_ExtensionsManager::singleton()->getExtensionById('taoQtiItem')->getDir(); $taoDir = \common_ext_ExtensionsManager::singleton()->getExtensionById('tao')->getDir(); $assetPath = $qtiItemDir . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . 'runtime' . DIRECTORY_SEPARATOR; $assetLibPath = $taoDir . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR; if (\tao_helpers_Mode::is('production')) { tao_helpers_File::copy($assetPath . 'qtiLoader.min.js', $publicDirectory . 'qtiLoader.min.js', false); } else { tao_helpers_File::copy($assetPath . 'qtiLoader.js', $publicDirectory . 'qtiLoader.js', false); tao_helpers_File::copy($assetLibPath . 'require.js', $publicDirectory . 'require.js', false); } // retrieve the media assets try { $qtiItem = $this->retrieveAssets($item, $language, $publicDirectory); //store variable qti elements data into the private directory $variableElements = $qtiService->getVariableElements($qtiItem); $serializedVariableElements = json_encode($variableElements); file_put_contents($privateFolder . 'variableElements.json', $serializedVariableElements); // render item based on the modified QtiItem $xhtml = $qtiService->renderQTIItem($qtiItem, $language); //note : no need to manually copy qti or other third party lib files, all dependencies are managed by requirejs // write index.html file_put_contents($publicDirectory . 'index.html', $xhtml); return new common_report_Report(common_report_Report::TYPE_SUCCESS, __('Successfully compiled "%s"', $language)); } catch (\tao_models_classes_FileNotFoundException $e) { return new common_report_Report(common_report_Report::TYPE_ERROR, __('Unable to retrieve asset "%s"', $e->getFilePath())); } }
/** * @param core_kernel_classes_Resource $item * @param string $lang * @param Directory $publicDirectory * @return qti\Item * @throws taoItems_models_classes_CompilationFailedException */ protected function retrieveAssets(core_kernel_classes_Resource $item, $lang, Directory $publicDirectory) { $qtiItem = Service::singleton()->getDataItemByRdfItem($item, $lang); $assetParser = new AssetParser($qtiItem, $publicDirectory); $assetParser->setGetSharedLibraries(false); $assetParser->setGetXinclude(false); $resolver = new ItemMediaResolver($item, $lang); $replacementList = array(); foreach ($assetParser->extract() as $type => $assets) { foreach ($assets as $assetUrl) { foreach (self::$BLACKLIST as $blacklist) { if (preg_match($blacklist, $assetUrl) === 1) { continue 2; } } $mediaAsset = $resolver->resolve($assetUrl); $mediaSource = $mediaAsset->getMediaSource(); $basename = $mediaSource->getBaseName($mediaAsset->getMediaIdentifier()); $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++; } $replacementList[$assetUrl] = $replacement; $tmpfile = $mediaSource->download($mediaAsset->getMediaIdentifier()); $fh = fopen($tmpfile, 'r'); $publicDirectory->writeStream($lang . '/' . $replacement, $fh); fclose($fh); unlink($tmpfile); //$fileStream = $mediaSource->getFileStream($mediaAsset->getMediaIdentifier()); //$publicDirectory->writeStream($lang.'/'.$replacement, $fileStream); } } $dom = new \DOMDocument('1.0', 'UTF-8'); if ($dom->loadXML($qtiItem->toXml()) === true) { $xpath = new \DOMXPath($dom); $attributeNodes = $xpath->query('//@*'); foreach ($attributeNodes as $node) { if (isset($replacementList[$node->value])) { $node->value = $replacementList[$node->value]; } } $attributeNodes = $xpath->query("//*[local-name()='entry']") ?: []; unset($xpath); foreach ($attributeNodes as $node) { $node->nodeValue = strtr(htmlentities($node->nodeValue, ENT_XML1), $replacementList); } } else { throw new taoItems_models_classes_CompilationFailedException('Unable to load XML'); } $qtiParser = new Parser($dom->saveXML()); $assetRetrievedQtiItem = $qtiParser->load(); //loadxinclude $xincludeLoader = new XIncludeLoader($assetRetrievedQtiItem, $resolver); $xincludeLoader->load(true); return $assetRetrievedQtiItem; }
protected function getPortableElementAssets(\core_kernel_classes_Resource $item, $lang) { $qtiItem = Service::singleton()->getDataItemByRdfItem($item, $lang); $directory = $this->getStorageDirectory($item, $lang); $assetParser = new AssetParser($qtiItem, $directory); $assetParser->setGetCustomElementDefinition(true); return $assetParser->extractPortableAssetElements(); }
/** * @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; }
public function testImportPCI() { $itemClass = $this->itemService->getRootClass(); $report = $this->importService->importQTIPACKFile($this->getSamplePath('/package/PCI/pcisample.zip'), $itemClass); $this->assertEquals(\common_report_Report::TYPE_SUCCESS, $report->getType()); $items = array(); foreach ($report as $itemReport) { $this->assertEquals(\common_report_Report::TYPE_SUCCESS, $itemReport->getType()); $data = $itemReport->getData(); if (!is_null($data)) { $items[] = $data; } } $this->assertEquals(1, count($items)); $item = \oat\taoQtiItem\model\qti\Service::singleton()->getDataItemByRdfItem($items[0], DEFAULT_LANG, false); $itemData = $item->toArray(); $itemDataElemetns = current($itemData['body']['elements']); //ensure that path prefixed with interaction identifies was not changed; $this->assertEquals($itemDataElemetns['entryPoint'], "adaptiveChoiceInteraction/runtime/adaptiveChoiceInteraction.js"); //ensure that interaction properties imported properly $this->assertTrue(isset($itemDataElemetns['properties']['choices'][0]['label'])); foreach ($items as $item) { $this->itemService->deleteItem($item); } }
/** * Compact the test and items in a single QTI-XML Compact Document. * * @return XmlCompactDocument. */ protected function compactTest() { $testService = taoQtiTest_models_classes_QtiTestService::singleton(); $test = $this->getResource(); common_Logger::t('Compacting QTI test ' . $test->getLabel() . '...'); $resolver = new taoQtiTest_helpers_ItemResolver(Service::singleton()); $originalDoc = $testService->getDoc($test); $compiledDoc = XmlCompactDocument::createFromXmlAssessmentTestDocument($originalDoc, $resolver, $resolver); common_Logger::t("QTI Test XML transformed in a compact version."); return $compiledDoc; }
public function saveItem() { $returnValue = array('success' => false); if ($this->hasRequestParameter('uri')) { $uri = urldecode($this->getRequestParameter('uri')); $xml = file_get_contents('php://input'); $rdfItem = new core_kernel_classes_Resource($uri); /** @var Service $itemService */ $itemService = Service::singleton(); //check if the item is QTI item if ($itemService->hasItemModel($rdfItem, array(ItemModel::MODEL_URI))) { try { $returnValue['success'] = $itemService->saveXmlItemToRdfItem($xml, $rdfItem); } catch (QtiModelException $e) { $returnValue = array('success' => false, 'type' => 'Error', 'message' => $e->getUserMessage()); } } } $this->returnJson($returnValue); }
/** * imports a qti package and * returns the number of items imported * * @access public * @author Joel Bout, <*****@*****.**> * @param $file * @param core_kernel_classes_Class $itemClass * @param bool $validate * @param core_kernel_versioning_Repository $repository * @param bool $rollbackOnError * @param bool $rollbackOnWarning * @throws Exception * @throws ExtractException * @throws ParsingException * @throws \common_Exception * @throws \common_ext_ExtensionException * @throws common_exception_Error * @return common_report_Report */ public function importQTIPACKFile($file, core_kernel_classes_Class $itemClass, $validate = true, core_kernel_versioning_Repository $repository = null, $rollbackOnError = false, $rollbackOnWarning = false) { //repository $repository = is_null($repository) ? taoItems_models_classes_ItemsService::singleton()->getDefaultFileSource() : $repository; $report = new common_report_Report(common_report_Report::TYPE_SUCCESS, ''); //load and validate the package $qtiPackageParser = new PackageParser($file); if ($validate) { $qtiPackageParser->validate(); if (!$qtiPackageParser->isValid()) { throw new ParsingException('Invalid QTI package format'); } } //extract the package $folder = $qtiPackageParser->extract(); if (!is_dir($folder)) { throw new ExtractException(); } //load and validate the manifest $qtiManifestParser = new ManifestParser($folder . 'imsmanifest.xml'); if ($validate) { $basePath = common_ext_ExtensionsManager::singleton()->getExtensionById('taoQtiItem')->getDir(); $this->validateMultiple($qtiManifestParser, array($basePath . 'models/classes/QTI/data/imscp_v1p1.xsd', $basePath . 'models/classes/QTI/data/apipv1p0/Core_Level/Package/apipv1p0_imscpv1p2_v1p0.xsd')); if (!$qtiManifestParser->isValid()) { tao_helpers_File::delTree($folder); $eStrs = array(); foreach ($qtiManifestParser->getErrors() as $libXmlError) { $eStrs[] = __('XML error at line %1$d "%2$s".', $libXmlError['line'], str_replace('[LibXMLError] ', '', trim($libXmlError['message']))); } $report->add(new common_report_Report(common_report_Report::TYPE_ERROR, __("The IMS Manifest file could not be validated:\n%s", implode($eStrs, "\n")))); $report->setType(common_report_Report::TYPE_ERROR); $report->setMessage(__("No Items could be imported from the given IMS QTI package.")); return $report; } } try { //load the information about resources in the manifest $qtiItemResources = $qtiManifestParser->load(); $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($qtiManifestParser->getSource()); $metadataMapping = $qtiService->getMetadataRegistry()->getMapping(); $metadataInjectors = array(); $metadataValues = array(); foreach ($metadataMapping['injectors'] as $injector) { $metadataInjectors[] = new $injector(); } foreach ($metadataMapping['extractors'] as $extractor) { $metadataExtractor = new $extractor(); $metadataValues = array_merge($metadataValues, $metadataExtractor->extract($domManifest)); } $successItems = array(); $successCount = 0; $itemCount = 0; foreach ($qtiItemResources as $qtiItemResource) { $itemCount++; try { $qtiFile = $folder . $qtiItemResource->getFile(); $itemReport = $this->importQTIFile($qtiFile, $itemClass, $validate, $repository); $rdfItem = $itemReport->getData(); if ($rdfItem) { $itemPath = taoItems_models_classes_ItemsService::singleton()->getItemFolder($rdfItem); $itemContent = $itemService->getItemContent($rdfItem); foreach ($qtiItemResource->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); $relPath = helpers_File::getRelPath($qtiFile, $auxPath); //prevent directory traversal: $relPathSafe = str_replace('..' . DIRECTORY_SEPARATOR, '', $relPath, $count); if ($count) { $itemContent = str_replace($relPath, $relPathSafe, $itemContent); } $destPath = $itemPath . $relPathSafe; tao_helpers_File::copy($auxPath, $destPath, true); } // Finally, import metadata. $this->importItemMetadata($metadataValues, $qtiItemResource, $rdfItem, $metadataInjectors); $itemService->setItemContent($rdfItem, $itemContent); $successItems[$qtiItemResource->getIdentifier()] = $rdfItem; $successCount++; } // Modify the message of the item report to include more specific // information e.g. the item identifier. if ($itemReport->containsError() === false) { $itemReport->setMessage(__('The IMS QTI Item referenced as "%s" in the IMS Manifest file was successfully imported.', $qtiItemResource->getIdentifier())); } else { $itemReport->setMessage(__('The IMS QTI Item referenced as "%s" in the IMS Manifest file could not be imported.', $qtiItemResource->getIdentifier())); } $report->add($itemReport); } catch (ParsingException $e) { $report->add(new common_report_Report(common_report_Report::TYPE_ERROR, $e->getUserMessage())); } catch (Exception $e) { // an error occured during a specific item $report->add(new common_report_Report(common_report_Report::TYPE_ERROR, __("An unknown error occured while importing the IMS QTI Package."))); common_Logger::e($e->getMessage()); } } if ($successCount > 0) { // Some items were imported from the package. $report->setMessage(__('%d Item(s) of %d imported from the given IMS QTI Package.', $successCount, $itemCount)); if ($successCount !== $itemCount) { $report->setType(common_report_Report::TYPE_WARNING); } } else { $report->setMessage(__('No Items could be imported from the given IMS QTI package.')); $report->setType(common_report_Report::TYPE_ERROR); } if ($rollbackOnError === true) { if ($report->getType() === common_report_Report::TYPE_ERROR || $report->contains(common_report_Report::TYPE_ERROR)) { $this->rollback($successItems, $report); } } elseif ($rollbackOnWarning === true) { if ($report->getType() === common_report_Report::TYPE_WARNING || $report->contains(common_report_Report::TYPE_WARNING)) { $this->rollback($successItems, $report); } } } catch (common_exception_UserReadableException $e) { $report = new common_report_Report(common_report_Report::TYPE_ERROR, __($e->getUserMessage())); $report->add($e); } // cleanup tao_helpers_File::delTree($folder); return $report; }
/** * tests initialization */ public function setUp() { TaoPhpUnitTestRunner::initTest(); $this->qtiService = Service::singleton(); }
/** * Desploy all the required files into the provided directories * * @param core_kernel_classes_Resource $item * @param string $language * @param string $publicDirectory * @param string $privateFolder * @return common_report_Report */ protected function deployQtiItem(core_kernel_classes_Resource $item, $language, $publicDirectory, $privateFolder) { // start debugging here common_Logger::d('destination original ' . $publicDirectory . ' ' . $privateFolder); $itemService = taoItems_models_classes_ItemsService::singleton(); $qtiService = Service::singleton(); //copy all item folder (but the qti.xml) $itemFolder = $itemService->getItemFolder($item, $language); taoItems_helpers_Deployment::copyResources($itemFolder, $publicDirectory, array('qti.xml')); //copy item.xml file to private directory tao_helpers_File::copy($itemFolder . 'qti.xml', $privateFolder . 'qti.xml', false); //copy client side resources (javascript loader) $qtiItemDir = \common_ext_ExtensionsManager::singleton()->getExtensionById('taoQtiItem')->getDir(); $taoDir = \common_ext_ExtensionsManager::singleton()->getExtensionById('tao')->getDir(); $assetPath = $qtiItemDir . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . 'runtime' . DIRECTORY_SEPARATOR; $assetLibPath = $taoDir . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR; if (\tao_helpers_Mode::is('production')) { tao_helpers_File::copy($assetPath . 'qtiLoader.min.js', $publicDirectory . 'qtiLoader.min.js', false); } else { tao_helpers_File::copy($assetPath . 'qtiLoader.js', $publicDirectory . 'qtiLoader.js', false); tao_helpers_File::copy($assetLibPath . 'require.js', $publicDirectory . 'require.js', false); } //load item in php object : //warning: use the same instance of php qti item because the serial is regenerated each time: $qtiItem = $qtiService->getDataItemByRdfItem($item, $language); //store variable qti elements data into the private directory $variableElements = $qtiService->getVariableElements($qtiItem); $serializedVariableElements = json_encode($variableElements); file_put_contents($privateFolder . 'variableElements.json', $serializedVariableElements); // render item $xhtml = $qtiService->renderQTIItem($qtiItem, $language); // retrieve external resources $report = taoItems_helpers_Deployment::retrieveExternalResources($xhtml, $publicDirectory); //@todo (optional) : exclude 'require.js' from copying if ($report->getType() == common_report_Report::TYPE_SUCCESS) { $xhtml = $report->getData(); } else { return $report; } //note : no need to manually copy qti or other third party lib files, all dependencies are managed by requirejs // write index.html file_put_contents($publicDirectory . 'index.html', $xhtml); //copy the event.xml if not present $eventsXmlFile = $publicDirectory . 'events.xml'; if (!file_exists($eventsXmlFile)) { $qtiItemDir = \common_ext_ExtensionsManager::singleton()->getExtensionById('taoQtiItem')->getDir(); $eventsReference = $qtiItemDir . 'model' . DIRECTORY_SEPARATOR . 'qti' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'events_ref.xml'; $eventXml = file_get_contents($eventsReference); if (is_string($eventXml) && !empty($eventXml)) { $eventXml = str_replace('{ITEM_URI}', $item->getUri(), $eventXml); @file_put_contents($eventsXmlFile, $eventXml); } } // --- Include QTI specific compilation stuff here. // At this moment, we should have compiled raw Items. We now have // to put the qti.xml file for each language in the compilation folder. return new common_report_Report(common_report_Report::TYPE_SUCCESS, __('Successfully compiled "%s"', $language)); }
/** * Returns the JSON encoded map of availabled Portable Shared Libraries. * * Example: consider that the 'IMSGlobal/jquery_2_1_1' and 'OAT/lodash' shared * libraries are registered on the platform. This action will return the following * JSON structure: * * { * "IMSGlobal/jquery_2_1_1": "http://platformhost/taoQtiItem/views/js/portableSharedLibraries/IMSGlobal/jquery_2_1_1.js", * "OAT/lodash": "http://platformhost/taoQtiItem/views/js/portableSharedLibraries/OAT/lodash.js" * } */ public function index() { $registry = Service::singleton()->getSharedLibrariesRegistry(); $this->returnJson($registry->getMapping(), 200); }