instanceUri = $instanceUri; return $this; } /** * Returns a textual description of the import format * * @return string */ public function getLabel() { return __('Shared Stimulus'); } /** * Returns a form in order to prepare the import * if the import is from a file, the form should include the file element * * @return Form */ public function getForm() { return (new FileImportForm($this->instanceUri)) ->getForm(); } /** * Starts the import based on the form * * @param core_kernel_classes_Class $class * @param Form|array $form * @param string|null $userId owner of the resource * * @return Report $report * * @throws common_exception_Error */ public function import($class, $form, $userId = null) { $uploadedFile = $this->fetchUploadedFile($form); try { $service = $this->getMediaService(); $classUri = $class->getUri(); $instanceUri = $form instanceof Form ? $form->getValue('instanceUri') : (isset($form['instanceUri']) ? $form['instanceUri'] : null); $fileInfo = $form instanceof Form ? $form->getValue('source') : $form['source']; // importing new media if (!$instanceUri || $instanceUri === $classUri) { //if the file is a zip do a zip import if (!helpers_File::isZipMimeType($fileInfo['type'])) { try { self::isValidSharedStimulus($uploadedFile); $mediaResourceUri = $service->createMediaInstance( $uploadedFile, $classUri, tao_helpers_Uri::decode($form instanceof Form ? $form->getValue('lang') : $form['lang']), $fileInfo['name'], MediaService::SHARED_STIMULUS_MIME_TYPE, $userId ); if (!$mediaResourceUri) { $report = Report::createFailure(__('Fail to import Shared Stimulus')); $report->setData(['uriResource' => '']); } else { $report = Report::createSuccess(__('Shared Stimulus imported successfully')); $report->add(Report::createSuccess( __('Imported %s', $fileInfo['name']), ['uriResource' => $mediaResourceUri] // 'uriResource' key is needed by javascript in tao/views/templates/form/import.tpl )); } } catch (XmlStorageException $e) { // The shared stimulus is not qti compliant, display error $report = Report::createFailure($e->getMessage()); $report->setData(['uriResource' => '']); } } else { $report = $this->getZipImporter()->import($class, $form, $userId); } } else { if (!helpers_File::isZipMimeType($fileInfo['type'])) { self::isValidSharedStimulus($uploadedFile); if (in_array($fileInfo['type'], ['application/xml', 'text/xml'])) { $name = basename($fileInfo['name'], 'xml'); $name .= 'xhtml'; $filepath = tao_helpers_File::concat([dirname($fileInfo['name']), $name]); $fileResource = fopen($filepath, 'w'); $uploadedFileResource = $uploadedFile->readStream(); stream_copy_to_stream($uploadedFileResource, $fileResource); fclose($fileResource); fclose($uploadedFileResource); } $instanceEdited = $service->editMediaInstance( isset($filepath) ? $filepath : $uploadedFile, $instanceUri, tao_helpers_Uri::decode($form instanceof Form ? $form->getValue('lang') : $form['lang']), $userId ); if (!$instanceEdited) { $report = Report::createFailure(__('Fail to edit shared stimulus')); } else { $report = Report::createSuccess(__('Shared Stimulus edited successfully')); $report->add( Report::createSuccess( __('Edited %s', $fileInfo['name']), [ 'uriResource' => $instanceUri ] ) ); } $report->setData(['uriResource' => $instanceUri]); } else { $report = $this->getZipImporter()->edit(new core_kernel_classes_Resource($instanceUri), $form, $userId); } } } catch (Exception $e) { $message = $e instanceof common_exception_UserReadableException ? $e->getUserMessage() : __('An error has occurred. Please contact your administrator.'); $report = Report::createFailure($message); $report->setData(['uriResource' => '']); $this->logError($e->getMessage()); } $this->getUploadService()->remove($uploadedFile); return $report; } /** * @param string|File $file * * @return XmlDocument * * @throws XmlStorageException */ public static function isValidSharedStimulus($file) { // No $version given = auto detect. $xmlDocument = new XmlDocument(); // don't validate because of APIP if ($file instanceof File) { $xml = $file->read(); $xmlDocument->loadFromString($xml, false); } elseif (is_file($file) && is_readable($file)) { $xmlDocument->load($file, false); $xml = file_get_contents($file); } // The shared stimulus is qti compliant, see if it is not an interaction, feedback or template if (self::hasInteraction($xmlDocument->getDocumentComponent())) { throw new XmlStorageException("The shared stimulus contains interactions QTI components."); } if (self::hasFeedback($xmlDocument->getDocumentComponent())) { throw new XmlStorageException("The shared stimulus contains feedback QTI components."); } if (self::hasTemplate($xmlDocument->getDocumentComponent())) { throw new XmlStorageException("The shared stimulus contains template QTI components."); } ServiceManager::getServiceManager()->get(SharedStimulusMediaExtractor::class) ->assertMediaFileExists($xml); return $xmlDocument; } private static function hasInteraction(QtiComponent $domDocument): bool { $interactions = [ 'endAttemptInteraction', 'inlineChoiceInteraction', 'textEntryInteraction', 'associateInteraction', 'choiceInteraction', 'drawingInteraction', 'extendedTextInteraction', 'gapMatchInteraction', 'graphicAssociateInteraction', 'graphicGapMatchInteraction', 'graphicOrderInteraction', 'hotspotInteraction', 'selectPointInteraction', 'hottextInteraction', 'matchInteraction', 'mediaInteraction', 'orderInteraction', 'sliderInteraction', 'uploadInteraction', 'customInteraction', 'positionObjectInteraction', ]; return self::hasComponents($domDocument, $interactions); } private static function hasFeedback(QtiComponent $domDocument): bool { $feedback = [ 'feedbackBlock', 'feedbackInline' ]; return self::hasComponents($domDocument, $feedback); } private static function hasTemplate(QtiComponent $domDocument): bool { return self::hasComponents($domDocument, 'templateDeclaration'); } /** * @param string|string[] $className */ private static function hasComponents(QtiComponent $domDocument, $className): bool { return $domDocument->getComponentsByClassName($className)->count() > 0; } /** * @param SharedStimulusPackageImporter $zipImporter * @return $this */ public function setZipImporter($zipImporter) { $this->zipImporter = $zipImporter; return $this; } /** * Get the zip importer for shared stimulus * * @return SharedStimulusPackageImporter */ protected function getZipImporter() { if (!$this->zipImporter) { $this->zipImporter = new SharedStimulusPackageImporter(); $this->zipImporter->setServiceLocator($this->getServiceLocator()); } return $this->zipImporter; } /** * Defines the task parameters to be stored for later use. * * @param Form $form * @return array */ public function getTaskParameters(Form $form) { return array_merge( $form->getValues(), $this->getDefaultTaskParameters($form) ); } private function getMediaService(): MediaService { return $this->getServiceLocator()->get(MediaService::class); } }