diff --git a/config/default/TestModel.conf.php b/config/default/TestModel.conf.php index 9a0402ca7..6c376d33a 100644 --- a/config/default/TestModel.conf.php +++ b/config/default/TestModel.conf.php @@ -15,7 +15,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * Copyright (c) 2016-2017 (original work) Open Assessment Technologies SA; + * Copyright (c) 2016-2024 (original work) Open Assessment Technologies SA; */ use oat\taoQtiTest\models\compilation\CompilationService; @@ -26,6 +26,7 @@ new oat\taoQtiTest\models\export\Formats\Metadata\TestPackageExport(), new oat\taoQtiTest\models\export\Formats\Package2p1\TestPackageExport(), new oat\taoQtiTest\models\export\Formats\Package2p2\TestPackageExport(), + new oat\taoQtiTest\models\export\Formats\Package3p0\TestPackageExport(), ], 'importHandlers' => [ new taoQtiTest_models_classes_import_TestImport() diff --git a/migrations/Version202501141150192260_taoQtiTest.php b/migrations/Version202501141150192260_taoQtiTest.php new file mode 100644 index 000000000..113ba5218 --- /dev/null +++ b/migrations/Version202501141150192260_taoQtiTest.php @@ -0,0 +1,66 @@ +getServiceLocator()->get(TestModelService::SERVICE_ID); + + if (!$testModelService->hasOption('exportHandlers')) { + return; + } + + $handlers = $testModelService->getOption('exportHandlers'); + + $hasHandler = false; + foreach ($handlers as $handler) { + if ($handler instanceof TestPackageExport) { + $hasHandler = true; + break; + } + } + + if (!$hasHandler) { + $handlers[] = new TestPackageExport(); + $testModelService->setOption('exportHandlers', $handlers); + $this->getServiceLocator()->register(TestModelService::SERVICE_ID, $testModelService); + } + } + + public function down(Schema $schema): void + { + $testModelService = $this->getServiceLocator()->get(TestModelService::SERVICE_ID); + + if (!$testModelService->hasOption('exportHandlers')) { + return; + } + + $handlers = $testModelService->getOption('exportHandlers'); + + $filteredHandlers = array_filter($handlers, function($handler) { + return !($handler instanceof TestPackageExport); + }); + + $testModelService->setOption('exportHandlers', $filteredHandlers); + $this->getServiceLocator()->register(TestModelService::SERVICE_ID, $testModelService); + } +} diff --git a/models/classes/QtiTestUtils.php b/models/classes/QtiTestUtils.php index 28d306d82..304f2ea31 100644 --- a/models/classes/QtiTestUtils.php +++ b/models/classes/QtiTestUtils.php @@ -21,6 +21,7 @@ namespace oat\taoQtiTest\models; +use InvalidArgumentException; use oat\generis\Helper\SystemHelper; use oat\taoQtiItem\model\qti\Resource; use qtism\data\storage\xml\XmlDocument; @@ -28,6 +29,7 @@ use oat\oatbox\filesystem\Directory; use qtism\data\AssessmentTest; use oat\oatbox\service\ConfigurableService; +use taoItems_models_classes_TemplateRenderer; /** * Miscellaneous utility methods for the QtiTest extension. @@ -54,7 +56,7 @@ class QtiTestUtils extends ConfigurableService * @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 InvalidArgumentException If one of the above arguments is invalid. * @throws \common_Exception */ public function storeQtiResource(Directory $testContent, $qtiResource, $origin, $copy = true, $rename = '') @@ -71,7 +73,7 @@ public function storeQtiResource(Directory $testContent, $qtiResource, $origin, } elseif (is_string($qtiResource) === true) { $filePath = $qtiResource; } else { - throw new \InvalidArgumentException( + throw new InvalidArgumentException( "The 'qtiResource' argument must be a string or a taoQTI_models_classes_QTI_Resource object." ); } @@ -130,8 +132,16 @@ public function storeQtiResource(Directory $testContent, $qtiResource, $origin, */ public function emptyImsManifest($version = '2.1') { - $manifestFileName = ($version === '2.1') ? 'imsmanifest' : 'imsmanifestQti22'; - $templateRenderer = new \taoItems_models_classes_TemplateRenderer( + $manifestFileName = match ($version) { + '2.1' => 'imsmanifest', + '2.2' => 'imsmanifestQti22', + '3.0' => 'imsmanifestQti30', + default => throw new InvalidArgumentException( + 'Invalid version provided. Only "2.1", "2.2" and "3.0" are supported.' + ), + }; + + $templateRenderer = new taoItems_models_classes_TemplateRenderer( ROOT_PATH . 'taoQtiItem/model/qti/templates/' . $manifestFileName . '.tpl.php', [ 'qtiItems' => [], diff --git a/models/classes/export/Formats/Package3p0/QtiItemExporter.php b/models/classes/export/Formats/Package3p0/QtiItemExporter.php new file mode 100644 index 000000000..97f9d8d04 --- /dev/null +++ b/models/classes/export/Formats/Package3p0/QtiItemExporter.php @@ -0,0 +1,44 @@ +exporter = $exporter; + } + + public function __call(string $name, array $arguments) + { + return $this->exporter->$name(...$arguments); + } +} diff --git a/models/classes/export/Formats/Package3p0/QtiTestExporter.php b/models/classes/export/Formats/Package3p0/QtiTestExporter.php new file mode 100644 index 000000000..f9e872fa3 --- /dev/null +++ b/models/classes/export/Formats/Package3p0/QtiTestExporter.php @@ -0,0 +1,94 @@ +getExporterFactory(); + $this->exporter = $factory->create($item, $this->getZip(), $this->getManifest()); + + return new QtiItemExporter($this->exporter); + } + + protected function adjustTestXml(string $xml): string + { + return $this->itemContentPostProcessing($xml); + } + + protected function itemContentPostProcessing($content): string + { + $transformationService = $this->exporter->getTransformationService(); + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->loadXML($content); + + $newDom = new DOMDocument('1.0', 'UTF-8'); + $newDom->preserveWhiteSpace = false; + $newDom->formatOutput = true; + + $oldRoot = $dom->documentElement; + $newRoot = $newDom->createElement($transformationService->createQtiElementName($oldRoot->nodeName)); + + //QTI3 namespace + $newRoot->setAttribute('xmlns', self::QTI_SCHEMA_NAMESPACE); + $newRoot->setAttribute('xmlns:xsi', self::XML_SCHEMA_INSTANCE); + $newRoot->setAttribute( + 'xsi:schemaLocation', + sprintf('%s %s', self::XSI_SCHEMA_LOCATION, self::XSI_SCHEMA_LOCATION_XSD) + ); + + $transformationService->transformAttributes($oldRoot, $newRoot); + + $newDom->appendChild($newRoot); + + $transformationService->transformChildren($oldRoot, $newRoot, $newDom); + + return $newDom->saveXML(); + } + + private function getExporterFactory(): ExporterFactory + { + return $this->getServiceManager()->getContainer()->get(ExporterFactory::class); + } +} diff --git a/models/classes/export/Formats/Package3p0/TestPackageExport.php b/models/classes/export/Formats/Package3p0/TestPackageExport.php new file mode 100644 index 000000000..360b74d5f --- /dev/null +++ b/models/classes/export/Formats/Package3p0/TestPackageExport.php @@ -0,0 +1,56 @@ +getZip(), $this->getEmptyManifest()); + } +} + +// for backward compatibility +// phpcs:disable PSR1.Files.SideEffects +class_alias(TestPackageExport::class, 'taoQtiTest_models_classes_export_TestExport30'); +// phpcs:enable PSR1.Files.SideEffects