/** * 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; }
/** * Enables you to build the QTI_Resources from a manifest xml data node * Content Packaging 1.1) * * @access public * @author Joel Bout, <*****@*****.**> * @param SimpleXMLElement source * @return array * @see http://www.imsglobal.org/question/qti_v2p0/imsqti_intgv2p0.html#section10003 */ public static function getResourcesFromManifest(SimpleXMLElement $source) { $returnValue = array(); //check of the root tag if ($source->getName() != 'manifest') { throw new ParsingException("incorrect manifest root tag"); } $resourceNodes = $source->xpath("//*[name(.)='resource']"); foreach ($resourceNodes as $resourceNode) { $type = (string) $resourceNode['type']; if (Resource::isAssessmentItem($type)) { $id = (string) $resourceNode['identifier']; $href = isset($resourceNode['href']) ? (string) $resourceNode['href'] : ''; $auxFiles = array(); //parse for auxiliary files foreach ($resourceNode->file as $fileNode) { $fileHref = (string) $fileNode['href']; if ($href != $fileHref) { $auxFiles[] = $fileHref; } } //include dependency files in item foreach ($resourceNode->dependency as $dependencyNode) { $ref = (string) $dependencyNode['identifierref']; //find referenced files within the current manifest: $refResourceNodes = $source->xpath("//*[name(.)='resource' and @identifier='" . $ref . "']"); foreach ($refResourceNodes as $refResourceNode) { if (isset($refResourceNode['href'])) { $auxFiles[] = (string) $refResourceNode['href']; } } } $resource = new Resource($id, $type, $href); $resource->setAuxiliaryFiles($auxFiles); $returnValue[] = $resource; } } return (array) $returnValue; }
/** * Imports the auxiliary files (file elements contained in the resource test element to be imported) into * the TAO Test Content directory. * * If some file cannot be copied, warnings will be committed. * * @param core_kernel_file_File $testContent The pointer to the TAO Test Content directory where auxilliary files will be stored. * @param oat\taoQtiItem\model\qti\Resource $qtiResource The manifest resource describing the test to be imported. * @param string $extractionFolder The absolute path to the temporary folder containing the content of the imported IMS QTI Package Archive. * @param common_report_Report A report about how the importation behaved. */ protected function importTestAuxiliaryFiles(core_kernel_file_File $testContent, Resource $qtiResource, $extractionFolder, common_report_Report $report) { foreach ($qtiResource->getAuxiliaryFiles() as $aux) { try { taoQtiTest_helpers_Utils::storeQtiResource($testContent, $aux, $extractionFolder); } catch (common_Exception $e) { $report->add(new common_report_Report(common_report_Report::TYPE_WARNING, __('Auxiliary file not found at location "%s".', $aux))); } } }
/** * Import metadata to a given QTI Item. * * @param oat\taoQtiItem\model\qti\metadata\MetadataValue[] $metadataValues An array of MetadataValue objects. * @param Resource $qtiResource The object representing the QTI Resource, from an IMS Manifest perspective. * @param core_kernel_classes_Resource $resource The object representing the target QTI Item in the Ontology. * @param oat\taoQtiItem\model\qti\metadata\MetadataInjector[] $ontologyInjectors Implementations of MetadataInjector that will take care to inject the metadata values in the appropriate Ontology Resource Properties. * @throws oat\taoQtiItem\model\qti\metadata\MetadataInjectionException If an error occurs while importing the metadata. */ public function importItemMetadata(array $metadataValues, Resource $qtiResource, core_kernel_classes_Resource $resource, array $ontologyInjectors = array()) { // Filter metadata values for this given item. $identifier = $qtiResource->getIdentifier(); if (isset($metadataValues[$identifier]) === true) { $values = $metadataValues[$identifier]; foreach ($ontologyInjectors as $injector) { $injector->inject($resource, array($identifier => $values)); } } }
/** * 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; }
/** * Import metadata to a given QTI Item. * * @param oat\taoQtiItem\model\qti\metadata\MetadataValue[] $metadataValues An array of MetadataValue objects. * @param Resource $qtiResource The object representing the QTI Resource, from an IMS Manifest perspective. * @param core_kernel_classes_Resource $resource The object representing the target QTI Item in the Ontology. * @param oat\taoQtiItem\model\qti\metadata\MetadataInjector[] $ontologyInjectors Implementations of MetadataInjector that will take care to inject the metadata values in the appropriate Ontology Resource Properties. * @throws oat\taoQtiItem\model\qti\metadata\MetadataInjectionException If an error occurs while importing the metadata. */ public function importResourceMetadata(array $metadataValues, Resource $qtiResource, core_kernel_classes_Resource $resource, array $ontologyInjectors = array()) { // Filter metadata values for this given item. $identifier = $qtiResource->getIdentifier(); if (isset($metadataValues[$identifier]) === true) { \common_Logger::i("Preparing Metadata Values for resource '{$identifier}'..."); $values = $metadataValues[$identifier]; foreach ($ontologyInjectors as $injector) { $valuesCount = count($values); $injectorClass = get_class($injector); \common_Logger::i("Attempting to inject {$valuesCount} Metadata Values in database for resource '{$identifier}' with Metadata Injector '{$injectorClass}'."); $injector->inject($resource, array($identifier => $values)); } } }
/** * It is sometimes necessary to identify the link between assessmentItemRefs described in a QTI Test definition and the resources * describing items in IMS Manifest file. This utility method helps you to achieve this. * * The method will return an array describing the IMS Manifest resources that were found in an IMS Manifest file on basis of * the assessmentItemRefs found in an AssessmentTest definition. The keys of the arrays are assessmentItemRef identifiers and * values are IMS Manifest Resources. * * If an IMS Manifest Resource cannot be found for a given assessmentItemRef, the value in the returned array will be false. * * @param XmlDocument $test A QTI Test Definition. * @param taoQtiTest_models_classes_ManifestParser $manifestParser A Manifest Parser. * @param string $basePath The base path of the folder the IMS archive is exposed as a file system component. * @return array An array containing two arrays (items and dependencies) where keys are identifiers and values are oat\taoQtiItem\model\qti\Resource objects or false. */ public static function buildAssessmentItemRefsTestMap(XmlDocument $test, taoQtiTest_models_classes_ManifestParser $manifestParser, $basePath) { $assessmentItemRefs = $test->getDocumentComponent()->getComponentsByClassName('assessmentItemRef'); $map = array('items' => array(), 'dependencies' => array()); $itemResources = $manifestParser->getResources(Resource::getItemTypes(), taoQtiTest_models_classes_ManifestParser::FILTER_RESOURCE_TYPE); $allResources = $manifestParser->getResources(); // cleanup $basePath. $basePath = rtrim($basePath, "/\\"); $basePath = helpers_File::truePath($basePath); $basePath .= DIRECTORY_SEPARATOR; $documentURI = preg_replace("/^file:\\/{1,3}/", '', $test->getDomDocument()->documentURI); $testPathInfo = pathinfo($documentURI); $testBasePath = tao_helpers_File::truePath($testPathInfo['dirname']) . DIRECTORY_SEPARATOR; foreach ($assessmentItemRefs as $itemRef) { // Find the QTI Resource (in IMS Manifest) related to the item ref. // To achieve this, we compare their path. $itemRefRelativeHref = str_replace('/', DIRECTORY_SEPARATOR, $itemRef->getHref()); $itemRefRelativeHref = ltrim($itemRefRelativeHref, "/\\"); $itemRefCanonicalHref = helpers_File::truePath($testBasePath . $itemRefRelativeHref); $map['items'][$itemRef->getIdentifier()] = false; // Compare with items referenced in the manifest. foreach ($itemResources as $itemResource) { $itemResourceRelativeHref = str_replace('/', DIRECTORY_SEPARATOR, $itemResource->getFile()); $itemResourceRelativeHref = ltrim($itemResourceRelativeHref, "/\\"); $itemResourceCanonicalHref = helpers_File::truePath($basePath . $itemResourceRelativeHref); // With some Windows flavours (Win7, Win8), the $itemRefCanonicalHref comes out with // a leading 'file:\' component. Let's clean this. (str_replace is binary-safe \0/) $os = tao_helpers_Environment::getOperatingSystem(); if ($os === 'WINNT' || $os === 'WIN32' || $os === 'Windows') { $itemRefCanonicalHref = str_replace('file:\\', '', $itemRefCanonicalHref); // And moreover, it sometimes refer the temp directory as Windows\TEMP instead of Windows\Temp. $itemRefCanonicalHref = str_replace('\\TEMP\\', '\\Temp\\', $itemRefCanonicalHref); } // With some MacOS flavours, the $itemRefCanonicalHref comes out with // a leading '/private' component. Clean it! if ($os === 'Darwin') { $itemRefCanonicalHref = str_replace('/private', '', $itemRefCanonicalHref); } if ($itemResourceCanonicalHref == $itemRefCanonicalHref && is_file($itemResourceCanonicalHref)) { // assessmentItemRef <-> IMS Manifest resource successful binding! $map['items'][$itemRef->getIdentifier()] = $itemResource; //get dependencies for each item foreach ($itemResource->getDependencies() as $dependencyIdentifier) { /** @var taoQtiTest_models_classes_QtiResource $resource */ foreach ($allResources as $resource) { if ($dependencyIdentifier == $resource->getIdentifier()) { $map['dependencies'][$dependencyIdentifier] = $resource; break; } } } break; } } } return $map; }