From a2e1529695182a9d603dea1635089b635d1d28b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Mon, 24 Jun 2024 16:17:37 +0200 Subject: [PATCH 01/41] feat: create new qti identifier generation strategy --- manifest.php | 4 +- .../Version202406241409071101_taoQtiItem.php | 33 ++++++++ scripts/install/EnableUuidQtiIdentifier.php | 82 +++++++++++++++++++ views/js/qtiCreator/helper/itemLoader.js | 14 +++- views/js/qtiCreator/itemCreator.js | 8 +- 5 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 migrations/Version202406241409071101_taoQtiItem.php create mode 100644 scripts/install/EnableUuidQtiIdentifier.php diff --git a/manifest.php b/manifest.php index 4f31c657ff..fee814483a 100755 --- a/manifest.php +++ b/manifest.php @@ -34,6 +34,7 @@ use oat\taoQtiItem\model\qti\metadata\importer\MetaMetadataServiceProvider; use oat\taoQtiItem\model\qti\ServiceProvider\ItemIdentifierValidatorServiceProvider; use oat\taoQtiItem\model\qti\ServiceProvider\MetadataServiceProvider; +use oat\taoQtiItem\scripts\install\EnableUuidQtiIdentifier; use oat\taoQtiItem\scripts\install\InitMetadataService; use oat\taoQtiItem\scripts\install\ItemEventRegister; use oat\taoQtiItem\scripts\install\RegisterItemCompilerBlacklist; @@ -89,7 +90,8 @@ RegisterItemCompilerBlacklist::class, RegisterNpmPaths::class, ExtendConfigurationRegistry::class, - SetupQtiMetadataImportExportService::class + SetupQtiMetadataImportExportService::class, + EnableUuidQtiIdentifier::class ] ], 'local' => [ diff --git a/migrations/Version202406241409071101_taoQtiItem.php b/migrations/Version202406241409071101_taoQtiItem.php new file mode 100644 index 0000000000..15b9f6cefe --- /dev/null +++ b/migrations/Version202406241409071101_taoQtiItem.php @@ -0,0 +1,33 @@ +addReport( + $this->propagate(new EnableUuidQtiIdentifier())([]) + ); + } + + public function down(Schema $schema): void + { + } +} diff --git a/scripts/install/EnableUuidQtiIdentifier.php b/scripts/install/EnableUuidQtiIdentifier.php new file mode 100644 index 0000000000..05e7e949f2 --- /dev/null +++ b/scripts/install/EnableUuidQtiIdentifier.php @@ -0,0 +1,82 @@ +customIdentifierGenerationStrategyEnabled()) { + $this->addQtiCreatorConfig(); + $this->addClientLibRegistryEntry(); + } + } + + private function addClientLibRegistryEntry(): void + { + $taoExtension = $this->getServiceManager() + ->get(common_ext_ExtensionsManager::SERVICE_ID) + ->getExtensionById('tao'); + + $config = $taoExtension->getConfig('client_lib_config_registry'); + + if (!isset($config['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier'])) { + $config['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier'] = [ + 'qtiIdPattern' => '/^\d{9}$/' + ]; + } + + $taoExtension->setConfig('client_lib_config_registry', $config); + } + + private function addQtiCreatorConfig(): void + { + $taoQtiItemExtension = $this->getServiceManager() + ->get(common_ext_ExtensionsManager::SERVICE_ID) + ->getExtensionById('taoQtiItem'); + $config = $taoQtiItemExtension + ->getConfig('qtiCreator'); + + if (!isset($config['identifierGenerationStrategy'])) { + $config['identifierGenerationStrategy'] = 'uuid'; + } + + $taoQtiItemExtension->setConfig('qtiCreator', $config); + } + + private function customIdentifierGenerationStrategyEnabled(): bool + { + return $this->getServiceManager() + ->getContainer() + ->get(FeatureFlagChecker::class) + ->isEnabled(FeatureFlagCheckerInterface::FEATURE_FLAG_UUID_QTI_IDENTIFIER); + } +} + + diff --git a/views/js/qtiCreator/helper/itemLoader.js b/views/js/qtiCreator/helper/itemLoader.js index e24f6f153f..a6b5e1014a 100644 --- a/views/js/qtiCreator/helper/itemLoader.js +++ b/views/js/qtiCreator/helper/itemLoader.js @@ -33,6 +33,12 @@ define([ return uri.substring(pos + 1, pos + 1 + qtiIdentifier.maxQtiIdLength); }; + const _generateUuidIdentifier = function _generateUuidIdentifier() { + const dateString = Math.floor(Date.now() / 100000).toString().slice(0,7); + const randString = (Math.floor(Math.random() * 90) + 10).toString().slice(0, 2); + return dateString + randString; + } + const decodeHtml = function (str) { const map = { '&': '&', @@ -116,7 +122,13 @@ define([ callback(loadedItem, this.getLoadedClasses()); }); } else { - const newItem = new Item().id(_generateIdentifier(config.uri)).attr('title', config.label); + let uuid; + if (config.identifierGenerationStrategy === 'uuid') { + uuid = _generateUuidIdentifier(); + } else { + uuid = _generateIdentifier(config.uri); + } + const newItem = new Item().id(uuid).attr('title', config.label); newItem.createResponseProcessing(); diff --git a/views/js/qtiCreator/itemCreator.js b/views/js/qtiCreator/itemCreator.js index d8a89a33fb..1acc4eba51 100644 --- a/views/js/qtiCreator/itemCreator.js +++ b/views/js/qtiCreator/itemCreator.js @@ -71,13 +71,14 @@ define([ * @param {String} label - the item label * @param {String} itemDataUrl - the data url * @param {Boolean} perInteractionRp - per interaction processing enabled + * @param {String} identifierGenerationStrategy - per interaction processing enabled * * @returns {Promise} that resolve with the loaded item model */ - const loadItem = function loadItem(uri, label, itemDataUrl, perInteractionRp) { + const loadItem = function loadItem(uri, label, itemDataUrl, perInteractionRp, identifierGenerationStrategy) { return new Promise(function (resolve, reject) { itemLoader.loadItem( - { uri: uri, label: label, itemDataUrl: itemDataUrl, perInteractionRp }, + { uri: uri, label: label, itemDataUrl: itemDataUrl, perInteractionRp, identifierGenerationStrategy }, function (item) { if (!item) { reject(new Error('Unable to load the item')); @@ -240,7 +241,8 @@ define([ config.properties.uri, config.properties.label, config.properties.itemDataUrl, - config.properties.perInteractionRp + config.properties.perInteractionRp, + config.properties.identifierGenerationStrategy ) .then(function (item) { if (!_.isObject(item)) { From 684e9b4cd0ec5ee0977fa6154f971a226a85a672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Tue, 25 Jun 2024 08:57:46 +0200 Subject: [PATCH 02/41] feat: phpcs --- scripts/install/EnableUuidQtiIdentifier.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/install/EnableUuidQtiIdentifier.php b/scripts/install/EnableUuidQtiIdentifier.php index 05e7e949f2..1e53b58649 100644 --- a/scripts/install/EnableUuidQtiIdentifier.php +++ b/scripts/install/EnableUuidQtiIdentifier.php @@ -29,7 +29,6 @@ class EnableUuidQtiIdentifier extends InstallAction { - public function __invoke($params): void { if ($this->customIdentifierGenerationStrategyEnabled()) { @@ -78,5 +77,3 @@ private function customIdentifierGenerationStrategyEnabled(): bool ->isEnabled(FeatureFlagCheckerInterface::FEATURE_FLAG_UUID_QTI_IDENTIFIER); } } - - From 027082456cb5448a5e9beb9d01deaf7ab3356a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Tue, 25 Jun 2024 13:52:26 +0200 Subject: [PATCH 03/41] feat: Rename UUID Generation strategy to UniqueNumeric --- manifest.php | 4 ++-- migrations/Version202406241409071101_taoQtiItem.php | 4 ++-- ...tifier.php => EnableUniqueNumericQtiIdentifier.php} | 10 +++++----- views/js/qtiCreator/helper/itemLoader.js | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) rename scripts/install/{EnableUuidQtiIdentifier.php => EnableUniqueNumericQtiIdentifier.php} (88%) diff --git a/manifest.php b/manifest.php index fee814483a..844691f2cc 100755 --- a/manifest.php +++ b/manifest.php @@ -34,7 +34,7 @@ use oat\taoQtiItem\model\qti\metadata\importer\MetaMetadataServiceProvider; use oat\taoQtiItem\model\qti\ServiceProvider\ItemIdentifierValidatorServiceProvider; use oat\taoQtiItem\model\qti\ServiceProvider\MetadataServiceProvider; -use oat\taoQtiItem\scripts\install\EnableUuidQtiIdentifier; +use oat\taoQtiItem\scripts\install\EnableUniqueNumericQtiIdentifier; use oat\taoQtiItem\scripts\install\InitMetadataService; use oat\taoQtiItem\scripts\install\ItemEventRegister; use oat\taoQtiItem\scripts\install\RegisterItemCompilerBlacklist; @@ -91,7 +91,7 @@ RegisterNpmPaths::class, ExtendConfigurationRegistry::class, SetupQtiMetadataImportExportService::class, - EnableUuidQtiIdentifier::class + EnableUniqueNumericQtiIdentifier::class ] ], 'local' => [ diff --git a/migrations/Version202406241409071101_taoQtiItem.php b/migrations/Version202406241409071101_taoQtiItem.php index 15b9f6cefe..dc2eaf498c 100644 --- a/migrations/Version202406241409071101_taoQtiItem.php +++ b/migrations/Version202406241409071101_taoQtiItem.php @@ -6,7 +6,7 @@ use Doctrine\DBAL\Schema\Schema; use oat\tao\scripts\tools\migrations\AbstractMigration; -use oat\taoQtiItem\scripts\install\EnableUuidQtiIdentifier; +use oat\taoQtiItem\scripts\install\EnableUniqueNumericQtiIdentifier; /** * Auto-generated Migration: Please modify to your needs! @@ -23,7 +23,7 @@ public function getDescription(): string public function up(Schema $schema): void { $this->addReport( - $this->propagate(new EnableUuidQtiIdentifier())([]) + $this->propagate(new EnableUniqueNumericQtiIdentifier())([]) ); } diff --git a/scripts/install/EnableUuidQtiIdentifier.php b/scripts/install/EnableUniqueNumericQtiIdentifier.php similarity index 88% rename from scripts/install/EnableUuidQtiIdentifier.php rename to scripts/install/EnableUniqueNumericQtiIdentifier.php index 1e53b58649..739f835c55 100644 --- a/scripts/install/EnableUuidQtiIdentifier.php +++ b/scripts/install/EnableUniqueNumericQtiIdentifier.php @@ -27,11 +27,11 @@ use oat\tao\model\featureFlag\FeatureFlagChecker; use oat\tao\model\featureFlag\FeatureFlagCheckerInterface; -class EnableUuidQtiIdentifier extends InstallAction +class EnableUniqueNumericQtiIdentifier extends InstallAction { public function __invoke($params): void { - if ($this->customIdentifierGenerationStrategyEnabled()) { + if ($this->uniqueNumericIdentifierGenerationStrategyEnabled()) { $this->addQtiCreatorConfig(); $this->addClientLibRegistryEntry(); } @@ -63,17 +63,17 @@ private function addQtiCreatorConfig(): void ->getConfig('qtiCreator'); if (!isset($config['identifierGenerationStrategy'])) { - $config['identifierGenerationStrategy'] = 'uuid'; + $config['identifierGenerationStrategy'] = 'uniqueNumeric'; } $taoQtiItemExtension->setConfig('qtiCreator', $config); } - private function customIdentifierGenerationStrategyEnabled(): bool + private function uniqueNumericIdentifierGenerationStrategyEnabled(): bool { return $this->getServiceManager() ->getContainer() ->get(FeatureFlagChecker::class) - ->isEnabled(FeatureFlagCheckerInterface::FEATURE_FLAG_UUID_QTI_IDENTIFIER); + ->isEnabled(FeatureFlagCheckerInterface::FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER); } } diff --git a/views/js/qtiCreator/helper/itemLoader.js b/views/js/qtiCreator/helper/itemLoader.js index a6b5e1014a..0b0a565ea8 100644 --- a/views/js/qtiCreator/helper/itemLoader.js +++ b/views/js/qtiCreator/helper/itemLoader.js @@ -33,7 +33,7 @@ define([ return uri.substring(pos + 1, pos + 1 + qtiIdentifier.maxQtiIdLength); }; - const _generateUuidIdentifier = function _generateUuidIdentifier() { + const _generateUniqueNumericIdentifier = function _generateUniqueNumericIdentifier() { const dateString = Math.floor(Date.now() / 100000).toString().slice(0,7); const randString = (Math.floor(Math.random() * 90) + 10).toString().slice(0, 2); return dateString + randString; @@ -122,13 +122,13 @@ define([ callback(loadedItem, this.getLoadedClasses()); }); } else { - let uuid; + let identifier; if (config.identifierGenerationStrategy === 'uuid') { - uuid = _generateUuidIdentifier(); + identifier = _generateUniqueNumericIdentifier(); } else { - uuid = _generateIdentifier(config.uri); + identifier = _generateIdentifier(config.uri); } - const newItem = new Item().id(uuid).attr('title', config.label); + const newItem = new Item().id(identifier).attr('title', config.label); newItem.createResponseProcessing(); From f7f1ee7e78e19990d37d9a8353b27f28535cbdfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Tue, 25 Jun 2024 14:04:29 +0200 Subject: [PATCH 04/41] feat: Error tooltip modification --- scripts/install/EnableUniqueNumericQtiIdentifier.php | 3 ++- views/js/qtiCreator/widgets/helpers/qtiIdentifier.js | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/install/EnableUniqueNumericQtiIdentifier.php b/scripts/install/EnableUniqueNumericQtiIdentifier.php index 739f835c55..8370be5f99 100644 --- a/scripts/install/EnableUniqueNumericQtiIdentifier.php +++ b/scripts/install/EnableUniqueNumericQtiIdentifier.php @@ -47,7 +47,8 @@ private function addClientLibRegistryEntry(): void if (!isset($config['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier'])) { $config['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier'] = [ - 'qtiIdPattern' => '/^\d{9}$/' + 'qtiIdPattern' => '/^\d{9}$/', + 'invalidQtiIdMessage' => 'The QTI identifier must be a 9-digit number.', ]; } diff --git a/views/js/qtiCreator/widgets/helpers/qtiIdentifier.js b/views/js/qtiCreator/widgets/helpers/qtiIdentifier.js index 84391654e4..47beb445df 100644 --- a/views/js/qtiCreator/widgets/helpers/qtiIdentifier.js +++ b/views/js/qtiCreator/widgets/helpers/qtiIdentifier.js @@ -21,10 +21,10 @@ define(['module', 'i18n'], function (module, __) { const qtiIdPattern = module.config().qtiIdPattern || '/^[a-zA-Z_]{1}[a-zA-Z0-9_.-]*$/u'; const [, patternContent, flags] = qtiIdPattern.match(/^\/(.+)\/(\w*)$/); + const defaultInvalidQtiIdMessage = 'Identifiers must start with a letter or an underscore and contain only letters, numbers, dots, underscores ( _ ), or hyphens ( - ).'; + const message = module.config().invalidQtiIdMessage || defaultInvalidQtiIdMessage; + const invalidQtiIdMessage = __(message); - const invalidQtiIdMessage = __( - 'Identifiers must start with a letter or an underscore and contain only letters, numbers, dots, underscores ( _ ), or hyphens ( - ).' - ); return { pattern: new RegExp(patternContent, flags), invalidQtiIdMessage, From 3ad479cf4cdb735f78892262d29f77296b59794f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 09:30:52 +0200 Subject: [PATCH 05/41] feat: When importing item with UniqueNumeric we will regenerate identifier --- helpers/QtiXmlLoader.php | 87 ++++++++++++++++ manifest.php | 4 +- model/qti/ImportService.php | 12 +++ ...ifierGenerationStrategyServiceProvider.php | 52 ++++++++++ .../UniqueNumericQtiIdentifierReplacer.php | 62 ++++++++++++ test/unit/helpers/QtiXmlLoaderTest.php | 61 ++++++++++++ test/unit/helpers/qtiExamples/invalidQti.xml | 1 + test/unit/helpers/qtiExamples/qti.xml | 22 +++++ ...UniqueNumericQtiIdentifierReplacerTest.php | 98 +++++++++++++++++++ test/unit/model/qti/parser/qti.xml | 22 +++++ 10 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 helpers/QtiXmlLoader.php create mode 100644 model/qti/ServiceProvider/IdentifierGenerationStrategyServiceProvider.php create mode 100644 model/qti/parser/UniqueNumericQtiIdentifierReplacer.php create mode 100644 test/unit/helpers/QtiXmlLoaderTest.php create mode 100644 test/unit/helpers/qtiExamples/invalidQti.xml create mode 100644 test/unit/helpers/qtiExamples/qti.xml create mode 100644 test/unit/model/qti/parser/UniqueNumericQtiIdentifierReplacerTest.php create mode 100644 test/unit/model/qti/parser/qti.xml diff --git a/helpers/QtiXmlLoader.php b/helpers/QtiXmlLoader.php new file mode 100644 index 0000000000..f364f0694f --- /dev/null +++ b/helpers/QtiXmlLoader.php @@ -0,0 +1,87 @@ +extensionsManager = $extensionsManager; + } + + /** + * Load QTI xml and return DOMDocument instance. + * This is service implementation of oat\taoQtiItem\helpers\Authoring::loadQtiXml + * @throws QtiModelException + */ + public function load(string $xml): DOMDocument + { + $dom = new DOMDocument('1.0', 'UTF-8'); + $this->configDomParser($dom); + try { + $dom->loadXML($xml); + } catch (Exception $e) { + throw new QtiModelException('Invalid QTI XML', 0, $e); + } + + return $dom; + } + + private function getQtiParserConfig(): array + { + return $this->extensionsManager->getExtensionById('taoQtiItem') + ->getConfig('XMLParser'); + } + + private function configDomParser(DOMDocument $dom): void + { + $parserConfig = $this->getQtiParserConfig(); + if ($this->parserConfigValid($parserConfig)) { + $dom->formatOutput = $parserConfig['formatOutput']; + $dom->preserveWhiteSpace = $parserConfig['preserveWhiteSpace']; + $dom->validateOnParse = $parserConfig['validateOnParse']; + + return; + } + + $dom->formatOutput = true; + $dom->preserveWhiteSpace = false; + $dom->validateOnParse = false; + } + + private function parserConfigValid(array $parserConfig): bool + { + return !empty($parserConfig) && + isset($parserConfig['formatOutput'], $parserConfig['preserveWhiteSpace'], $parserConfig['validateOnParse']); + } +} diff --git a/manifest.php b/manifest.php index 844691f2cc..f77fff64e5 100755 --- a/manifest.php +++ b/manifest.php @@ -32,6 +32,7 @@ }; use oat\taoQtiItem\model\FeatureFlag\ServiceProvider\FeatureFlagFlaServiceProvider; use oat\taoQtiItem\model\qti\metadata\importer\MetaMetadataServiceProvider; +use oat\taoQtiItem\model\qti\ServiceProvider\IdentifierGenerationStrategyServiceProvider; use oat\taoQtiItem\model\qti\ServiceProvider\ItemIdentifierValidatorServiceProvider; use oat\taoQtiItem\model\qti\ServiceProvider\MetadataServiceProvider; use oat\taoQtiItem\scripts\install\EnableUniqueNumericQtiIdentifier; @@ -204,6 +205,7 @@ FeatureFlagFlaServiceProvider::class, ItemIdentifierValidatorServiceProvider::class, MetadataServiceProvider::class, - MetaMetadataServiceProvider::class + MetaMetadataServiceProvider::class, + IdentifierGenerationStrategyServiceProvider::class, ], ]; diff --git a/model/qti/ImportService.php b/model/qti/ImportService.php index 90d7f739c4..f58cbb2d1f 100755 --- a/model/qti/ImportService.php +++ b/model/qti/ImportService.php @@ -54,6 +54,7 @@ use oat\taoQtiItem\model\qti\metadata\MetadataGuardianResource; use oat\taoQtiItem\model\qti\metadata\MetadataService; use oat\taoQtiItem\model\qti\metadata\ontology\MappedMetadataInjector; +use oat\taoQtiItem\model\qti\parser\UniqueNumericQtiIdentifierReplacer; use oat\taoQtiItem\model\qti\parser\ValidationException; use oat\taoQtiItem\model\event\ItemImported; use qtism\data\QtiComponentCollection; @@ -187,6 +188,7 @@ protected function createRdfItem(core_kernel_classes_Class $itemClass, Item $qti protected function createQtiItemModel($qtiFile, $validate = true) { $qtiXml = Authoring::sanitizeQtiXml($qtiFile); + $qtiXml = $this->replaceUniqueNumericQtiIdentifier($qtiXml); //validate the file to import $qtiParser = new Parser($qtiXml); @@ -952,4 +954,14 @@ private function getMetaMetadataImportMapper(): MetaMetadataImportMapper { return $this->getServiceManager()->getContainer()->get(MetaMetadataImportMapper::class); } + + private function getUniqueNumericQtiIdentifierReplacer(): UniqueNumericQtiIdentifierReplacer + { + return $this->getServiceManager()->getContainer()->get(UniqueNumericQtiIdentifierReplacer::class); + } + + private function replaceUniqueNumericQtiIdentifier(string $qtiXml) + { + return $this->getUniqueNumericQtiIdentifierReplacer()->replace($qtiXml); + } } diff --git a/model/qti/ServiceProvider/IdentifierGenerationStrategyServiceProvider.php b/model/qti/ServiceProvider/IdentifierGenerationStrategyServiceProvider.php new file mode 100644 index 0000000000..638ac30e09 --- /dev/null +++ b/model/qti/ServiceProvider/IdentifierGenerationStrategyServiceProvider.php @@ -0,0 +1,52 @@ +services(); + + $services->set(QtiXmlLoader::class, QtiXmlLoader::class) + ->args([ + service(ExtensionsManager::SERVICE_ID) + ]); + + $services->set(UniqueNumericQtiIdentifierReplacer::class, UniqueNumericQtiIdentifierReplacer::class) + ->args([ + service(FeatureFlagChecker::class), + service(QtiXmlLoader::class) + ]) + ->public(); + } +} diff --git a/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php b/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php new file mode 100644 index 0000000000..a92873b780 --- /dev/null +++ b/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php @@ -0,0 +1,62 @@ +featureFlagChecker = $featureFlagChecker; + $this->qtiXmlLoader = $qtiXmlLoader; + } + public function replace(string $qti): string + { + if (!$this->featureFlagChecker->isEnabled(FeatureFlagChecker::FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER)) { + return $qti; + } + $doc = $this->qtiXmlLoader->load($qti); + + $xpath = new DOMXpath($doc); + $xpath->registerNamespace('qti', 'http://www.imsglobal.org/xsd/imsqti_v2p2'); + $identifierNodes = $xpath->query('//qti:assessmentItem/@identifier'); + + foreach ($identifierNodes as $identifierNode) { + $identifierNode->nodeValue = $this->getNumericIdentifier(); + } + + return $doc->saveXML(); + } + + private function getNumericIdentifier(): string + { + return substr((string)floor(time() / 1000), 0, 7) + . substr((string)floor(mt_rand(10, 100)), 0, 2); + } +} diff --git a/test/unit/helpers/QtiXmlLoaderTest.php b/test/unit/helpers/QtiXmlLoaderTest.php new file mode 100644 index 0000000000..dfe1847f1a --- /dev/null +++ b/test/unit/helpers/QtiXmlLoaderTest.php @@ -0,0 +1,61 @@ +extensionsManagerMock = $this->createMock(ExtensionsManager::class); + $this->extensionMock = $this->createMock(Extension::class); + $this->extensionsManagerMock->method('getExtensionById')->willReturn($this->extensionMock); + $this->extensionMock->method('getConfig')->willReturn([ + 'formatOutput' => true, + 'preserveWhiteSpace' => false, + 'validateOnParse' => false, + ]); + $this->subject = new QtiXmlLoader($this->extensionsManagerMock); + } + + public function testLoad() + { + $qti = file_get_contents(__DIR__ . '/qtiExamples/qti.xml'); + $sub = $this->subject->load($qti); + + $this->assertInstanceOf(DOMDocument::class, $sub); + } + + public function testInvalidLoad() + { + $this->expectException(QtiModelException::class); + $qti = file_get_contents(__DIR__ . '/qtiExamples/invalidQti.xml'); + $this->subject->load($qti); + } +} diff --git a/test/unit/helpers/qtiExamples/invalidQti.xml b/test/unit/helpers/qtiExamples/invalidQti.xml new file mode 100644 index 0000000000..0c96779aba --- /dev/null +++ b/test/unit/helpers/qtiExamples/invalidQti.xml @@ -0,0 +1 @@ +This cannot be loaded diff --git a/test/unit/helpers/qtiExamples/qti.xml b/test/unit/helpers/qtiExamples/qti.xml new file mode 100644 index 0000000000..e242179204 --- /dev/null +++ b/test/unit/helpers/qtiExamples/qti.xml @@ -0,0 +1,22 @@ + + + + + + + 0 + + + +
+
+ + choice #1 + choice #2 + choice #3 + +
+
+
+ +
diff --git a/test/unit/model/qti/parser/UniqueNumericQtiIdentifierReplacerTest.php b/test/unit/model/qti/parser/UniqueNumericQtiIdentifierReplacerTest.php new file mode 100644 index 0000000000..c9f55ed9a7 --- /dev/null +++ b/test/unit/model/qti/parser/UniqueNumericQtiIdentifierReplacerTest.php @@ -0,0 +1,98 @@ +featureFlagCheckerMock = $this->createMock(FeatureFlagChecker::class); + $this->qtiXmlLoaderMock = $this->createMock(QtiXmlLoader::class); + + $this->subject = new UniqueNumericQtiIdentifierReplacer( + $this->featureFlagCheckerMock, + $this->qtiXmlLoaderMock + ); + } + + public function testReplace(): void + { + + $qti = file_get_contents(__DIR__ . '/qti.xml'); + $this->featureFlagCheckerMock->method('isEnabled') + ->willReturn(true); + + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->loadXML($qti); + + $xpath = new DOMXpath($dom); + $xpath->registerNamespace('qti', 'http://www.imsglobal.org/xsd/imsqti_v2p2'); + $identifierNodes = $xpath->query('//qti:assessmentItem/@identifier'); + $initialValue = $identifierNodes->item(0)->nodeValue; + + $this->qtiXmlLoaderMock->method('load') + ->willReturn($dom); + + $this->subject->replace($qti); + + $xpath = new DOMXpath($dom); + $xpath->registerNamespace('qti', 'http://www.imsglobal.org/xsd/imsqti_v2p2'); + $identifierNodes = $xpath->query('//qti:assessmentItem/@identifier'); + $this->assertRegExp('/\d{9}/', $identifierNodes->item(0)->nodeValue); + $this->assertNotEquals($initialValue, $identifierNodes->item(0)->nodeValue); + } + + public function testReplaceWhenFeatureFlagDisabled(): void + { + + $qti = file_get_contents(__DIR__ . '/qti.xml'); + $this->featureFlagCheckerMock->method('isEnabled') + ->willReturn(false); + + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->loadXML($qti); + + $xpath = new DOMXpath($dom); + $xpath->registerNamespace('qti', 'http://www.imsglobal.org/xsd/imsqti_v2p2'); + $identifierNodes = $xpath->query('//qti:assessmentItem/@identifier'); + $initialValue = $identifierNodes->item(0)->nodeValue; + + $this->qtiXmlLoaderMock->method('load') + ->willReturn($dom); + + $this->subject->replace($qti); + + $xpath = new DOMXpath($dom); + $xpath->registerNamespace('qti', 'http://www.imsglobal.org/xsd/imsqti_v2p2'); + $identifierNodes = $xpath->query('//qti:assessmentItem/@identifier'); + $this->assertEquals($initialValue, $identifierNodes->item(0)->nodeValue); + } +} diff --git a/test/unit/model/qti/parser/qti.xml b/test/unit/model/qti/parser/qti.xml new file mode 100644 index 0000000000..e242179204 --- /dev/null +++ b/test/unit/model/qti/parser/qti.xml @@ -0,0 +1,22 @@ + + + + + + + 0 + + + +
+
+ + choice #1 + choice #2 + choice #3 + +
+
+
+ +
From 9b650830b67f2298b169da223990e47954051569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 09:53:25 +0200 Subject: [PATCH 06/41] feat: temp fix composer --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4938a574c0..ab7b3301fe 100644 --- a/composer.json +++ b/composer.json @@ -66,7 +66,7 @@ "oat-sa/oatbox-extension-installer": "~1.1||dev-master", "naneau/semver": "~0.0.7", "oat-sa/generis": ">=15.36.4", - "oat-sa/tao-core": ">=54.14.7", + "oat-sa/tao-core": "dev-feat/AUT-3705/uuid-qti-identifier as 54.14.7", "oat-sa/extension-tao-item": ">=12.0.0", "oat-sa/extension-tao-test": ">=16.0.0" }, From 48db0298ef4dad8394f6b9d7a77e3e7b10c7f998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 13:32:35 +0200 Subject: [PATCH 07/41] feat: Change to dynamic config --- manifest.php | 6 +- .../Version202406241409071101_taoQtiItem.php | 33 -------- ...eatureFlagQtiIdentifierServiceProvider.php | 67 ++++++++++++++++ ...UniqueNumericQtiIdentifierClientConfig.php | 43 ++++++++++ .../UniqueNumericQtiIdentifierQtiCreator.php | 45 +++++++++++ .../EnableUniqueNumericQtiIdentifier.php | 80 ------------------- views/js/qtiCreator/helper/itemLoader.js | 2 +- 7 files changed, 159 insertions(+), 117 deletions(-) delete mode 100644 migrations/Version202406241409071101_taoQtiItem.php create mode 100644 model/FeatureFlag/ServiceProvider/FeatureFlagQtiIdentifierServiceProvider.php create mode 100644 model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php create mode 100644 model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php delete mode 100644 scripts/install/EnableUniqueNumericQtiIdentifier.php diff --git a/manifest.php b/manifest.php index 844691f2cc..e0e6004c40 100755 --- a/manifest.php +++ b/manifest.php @@ -31,10 +31,10 @@ CustomInteractionAssetExtractorAllocatorServiceProvider }; use oat\taoQtiItem\model\FeatureFlag\ServiceProvider\FeatureFlagFlaServiceProvider; +use oat\taoQtiItem\model\FeatureFlag\ServiceProvider\FeatureFlagQtiIdentifierServiceProvider; use oat\taoQtiItem\model\qti\metadata\importer\MetaMetadataServiceProvider; use oat\taoQtiItem\model\qti\ServiceProvider\ItemIdentifierValidatorServiceProvider; use oat\taoQtiItem\model\qti\ServiceProvider\MetadataServiceProvider; -use oat\taoQtiItem\scripts\install\EnableUniqueNumericQtiIdentifier; use oat\taoQtiItem\scripts\install\InitMetadataService; use oat\taoQtiItem\scripts\install\ItemEventRegister; use oat\taoQtiItem\scripts\install\RegisterItemCompilerBlacklist; @@ -91,7 +91,6 @@ RegisterNpmPaths::class, ExtendConfigurationRegistry::class, SetupQtiMetadataImportExportService::class, - EnableUniqueNumericQtiIdentifier::class ] ], 'local' => [ @@ -204,6 +203,7 @@ FeatureFlagFlaServiceProvider::class, ItemIdentifierValidatorServiceProvider::class, MetadataServiceProvider::class, - MetaMetadataServiceProvider::class + MetaMetadataServiceProvider::class, + FeatureFlagQtiIdentifierServiceProvider::class, ], ]; diff --git a/migrations/Version202406241409071101_taoQtiItem.php b/migrations/Version202406241409071101_taoQtiItem.php deleted file mode 100644 index dc2eaf498c..0000000000 --- a/migrations/Version202406241409071101_taoQtiItem.php +++ /dev/null @@ -1,33 +0,0 @@ -addReport( - $this->propagate(new EnableUniqueNumericQtiIdentifier())([]) - ); - } - - public function down(Schema $schema): void - { - } -} diff --git a/model/FeatureFlag/ServiceProvider/FeatureFlagQtiIdentifierServiceProvider.php b/model/FeatureFlag/ServiceProvider/FeatureFlagQtiIdentifierServiceProvider.php new file mode 100644 index 0000000000..7c1e59f171 --- /dev/null +++ b/model/FeatureFlag/ServiceProvider/FeatureFlagQtiIdentifierServiceProvider.php @@ -0,0 +1,67 @@ +services(); + + $services + ->set(UniqueNumericQtiIdentifierClientConfig::class) + ->public() + ->args( + [ + service(FeatureFlagChecker::class), + ] + ); + + $services->get(FeatureFlagConfigSwitcher::class) + ->call( + 'addClientConfigHandler', + [ + UniqueNumericQtiIdentifierClientConfig::class, + ] + )->call( + 'addExtensionConfigHandler', + [ + 'taoQtiItem', + 'qtiCreator', + UniqueNumericQtiIdentifierQtiCreator::class + ] + ); + } +} diff --git a/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php b/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php new file mode 100644 index 0000000000..a852e72967 --- /dev/null +++ b/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php @@ -0,0 +1,43 @@ +featureFlagChecker = $featureFlagChecker; + } + public function __invoke(array $configs): array + { + if ($this->featureFlagChecker->isEnabled('FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER')) { + $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['qtiIdPattern'] = '/^\\d{9}$/'; + $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['invalidQtiIdMessage'] = 'The QTI identifier must be a 9-digit number.'; + } + + return $configs; + } +} diff --git a/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php b/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php new file mode 100644 index 0000000000..fa8ab02bff --- /dev/null +++ b/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php @@ -0,0 +1,45 @@ +featureFlagChecker = $featureFlagChecker; + } + + public function __invoke(array $configs): array + { + if (!$this->featureFlagChecker->isEnabled('FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER')) { + $configs['identifierGenerationStrategy'] = 'uniqueNumeric'; + } + + return $configs; + } +} diff --git a/scripts/install/EnableUniqueNumericQtiIdentifier.php b/scripts/install/EnableUniqueNumericQtiIdentifier.php deleted file mode 100644 index 8370be5f99..0000000000 --- a/scripts/install/EnableUniqueNumericQtiIdentifier.php +++ /dev/null @@ -1,80 +0,0 @@ -uniqueNumericIdentifierGenerationStrategyEnabled()) { - $this->addQtiCreatorConfig(); - $this->addClientLibRegistryEntry(); - } - } - - private function addClientLibRegistryEntry(): void - { - $taoExtension = $this->getServiceManager() - ->get(common_ext_ExtensionsManager::SERVICE_ID) - ->getExtensionById('tao'); - - $config = $taoExtension->getConfig('client_lib_config_registry'); - - if (!isset($config['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier'])) { - $config['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier'] = [ - 'qtiIdPattern' => '/^\d{9}$/', - 'invalidQtiIdMessage' => 'The QTI identifier must be a 9-digit number.', - ]; - } - - $taoExtension->setConfig('client_lib_config_registry', $config); - } - - private function addQtiCreatorConfig(): void - { - $taoQtiItemExtension = $this->getServiceManager() - ->get(common_ext_ExtensionsManager::SERVICE_ID) - ->getExtensionById('taoQtiItem'); - $config = $taoQtiItemExtension - ->getConfig('qtiCreator'); - - if (!isset($config['identifierGenerationStrategy'])) { - $config['identifierGenerationStrategy'] = 'uniqueNumeric'; - } - - $taoQtiItemExtension->setConfig('qtiCreator', $config); - } - - private function uniqueNumericIdentifierGenerationStrategyEnabled(): bool - { - return $this->getServiceManager() - ->getContainer() - ->get(FeatureFlagChecker::class) - ->isEnabled(FeatureFlagCheckerInterface::FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER); - } -} diff --git a/views/js/qtiCreator/helper/itemLoader.js b/views/js/qtiCreator/helper/itemLoader.js index 0b0a565ea8..2c2f7da724 100644 --- a/views/js/qtiCreator/helper/itemLoader.js +++ b/views/js/qtiCreator/helper/itemLoader.js @@ -123,7 +123,7 @@ define([ }); } else { let identifier; - if (config.identifierGenerationStrategy === 'uuid') { + if (config.identifierGenerationStrategy === 'uniqueNumeric') { identifier = _generateUniqueNumericIdentifier(); } else { identifier = _generateIdentifier(config.uri); From 75464e930e68f90bc7b1718b0de18c426f966232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 13:37:28 +0200 Subject: [PATCH 08/41] feat: phpcs --- .../FeatureFlagQtiIdentifierServiceProvider.php | 1 - model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/model/FeatureFlag/ServiceProvider/FeatureFlagQtiIdentifierServiceProvider.php b/model/FeatureFlag/ServiceProvider/FeatureFlagQtiIdentifierServiceProvider.php index 7c1e59f171..36541b8840 100644 --- a/model/FeatureFlag/ServiceProvider/FeatureFlagQtiIdentifierServiceProvider.php +++ b/model/FeatureFlag/ServiceProvider/FeatureFlagQtiIdentifierServiceProvider.php @@ -35,7 +35,6 @@ class FeatureFlagQtiIdentifierServiceProvider implements ContainerServiceProviderInterface { - public function __invoke(ContainerConfigurator $configurator): void { $services = $configurator->services(); diff --git a/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php b/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php index a852e72967..d0caef2802 100644 --- a/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php +++ b/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php @@ -35,7 +35,8 @@ public function __invoke(array $configs): array { if ($this->featureFlagChecker->isEnabled('FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER')) { $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['qtiIdPattern'] = '/^\\d{9}$/'; - $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['invalidQtiIdMessage'] = 'The QTI identifier must be a 9-digit number.'; + $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['invalidQtiIdMessage'] = + 'The QTI identifier must be a 9-digit number.'; } return $configs; From 08fd4f3b7a85ad5484ce7d141d9027697556d20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 14:01:14 +0200 Subject: [PATCH 09/41] feat: move FF to taoQtiItem --- .../FeatureFlag/UniqueNumericFeatureFlag.php | 27 +++++++++++++++++++ ...UniqueNumericQtiIdentifierClientConfig.php | 4 ++- .../UniqueNumericQtiIdentifierQtiCreator.php | 4 ++- 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 model/FeatureFlag/UniqueNumericFeatureFlag.php diff --git a/model/FeatureFlag/UniqueNumericFeatureFlag.php b/model/FeatureFlag/UniqueNumericFeatureFlag.php new file mode 100644 index 0000000000..fae659d870 --- /dev/null +++ b/model/FeatureFlag/UniqueNumericFeatureFlag.php @@ -0,0 +1,27 @@ +featureFlagChecker->isEnabled('FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER')) { + if ($this->featureFlagChecker + ->isEnabled(UniqueNumericFeatureFlag::FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER) + ) { $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['qtiIdPattern'] = '/^\\d{9}$/'; $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['invalidQtiIdMessage'] = 'The QTI identifier must be a 9-digit number.'; diff --git a/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php b/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php index fa8ab02bff..890727ff3e 100644 --- a/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php +++ b/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php @@ -36,7 +36,9 @@ public function __construct(FeatureFlagCheckerInterface $featureFlagChecker) public function __invoke(array $configs): array { - if (!$this->featureFlagChecker->isEnabled('FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER')) { + if ($this->featureFlagChecker + ->isEnabled(UniqueNumericFeatureFlag::FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER) + ) { $configs['identifierGenerationStrategy'] = 'uniqueNumeric'; } From 1c521bd9b9fb7b54b2f68cf08ec17195873987bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 14:11:52 +0200 Subject: [PATCH 10/41] feat: Add FF to readme --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3a790ae26a..f07cc268ac 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ php tao/scripts/taoUpdate.php ### Feature Flags -| Variable | Description | Default value | -|------------------|---------------------------------------------------------------------------|---------------| -| FEATURE_FLAG_FLA | Toggles certain media-interaction options' availability in item authoring | false | +| Variable | Description | Default value | +|---------------------------------------------|---------------------------------------------------------------------------|---------------| +| FEATURE_FLAG_FLA | Toggles certain media-interaction options' availability in item authoring | false | +| FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER | This will replace Item Qti Identifier to 9 digits non editable field | - | From 593e5002fbea14c076eaefbd89b4d6b602ef0a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 14:14:17 +0200 Subject: [PATCH 11/41] feat: phpcs --- .../UniqueNumericQtiIdentifierClientConfig.php | 10 +++++++--- .../UniqueNumericQtiIdentifierQtiCreator.php | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php b/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php index dcddb67cda..8cebfcb990 100644 --- a/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php +++ b/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php @@ -33,9 +33,7 @@ public function __construct(FeatureFlagCheckerInterface $featureFlagChecker) } public function __invoke(array $configs): array { - if ($this->featureFlagChecker - ->isEnabled(UniqueNumericFeatureFlag::FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER) - ) { + if ($this->isEnabled()) { $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['qtiIdPattern'] = '/^\\d{9}$/'; $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['invalidQtiIdMessage'] = 'The QTI identifier must be a 9-digit number.'; @@ -43,4 +41,10 @@ public function __invoke(array $configs): array return $configs; } + + private function isEnabled(): bool + { + return $this->featureFlagChecker + ->isEnabled(UniqueNumericFeatureFlag::FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER); + } } diff --git a/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php b/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php index 890727ff3e..c2ffb7533a 100644 --- a/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php +++ b/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php @@ -36,12 +36,16 @@ public function __construct(FeatureFlagCheckerInterface $featureFlagChecker) public function __invoke(array $configs): array { - if ($this->featureFlagChecker - ->isEnabled(UniqueNumericFeatureFlag::FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER) - ) { + if ($this->isEnabled()) { $configs['identifierGenerationStrategy'] = 'uniqueNumeric'; } return $configs; } + + private function isEnabled(): bool + { + return $this->featureFlagChecker + ->isEnabled(UniqueNumericFeatureFlag::FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER); + } } From 8da89f7c96c8834d92ff4940b71b9cfc9e820acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 14:15:31 +0200 Subject: [PATCH 12/41] feat: phpcs --- model/FeatureFlag/UniqueNumericFeatureFlag.php | 1 + 1 file changed, 1 insertion(+) diff --git a/model/FeatureFlag/UniqueNumericFeatureFlag.php b/model/FeatureFlag/UniqueNumericFeatureFlag.php index fae659d870..3fee412e0b 100644 --- a/model/FeatureFlag/UniqueNumericFeatureFlag.php +++ b/model/FeatureFlag/UniqueNumericFeatureFlag.php @@ -21,6 +21,7 @@ declare(strict_types=1); namespace oat\taoQtiItem\model\FeatureFlag; + interface UniqueNumericFeatureFlag { public const FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER = 'FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER'; From 5853dab583dcbd3cd167d46eb94fae9d93f98add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 15:22:45 +0200 Subject: [PATCH 13/41] feat: revert , --- manifest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.php b/manifest.php index e0e6004c40..d8c34b3945 100755 --- a/manifest.php +++ b/manifest.php @@ -90,7 +90,7 @@ RegisterItemCompilerBlacklist::class, RegisterNpmPaths::class, ExtendConfigurationRegistry::class, - SetupQtiMetadataImportExportService::class, + SetupQtiMetadataImportExportService::class ] ], 'local' => [ From bc943e58b171275ded91e39655fde04cfa577aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 09:30:52 +0200 Subject: [PATCH 14/41] feat: When importing item with UniqueNumeric we will regenerate identifier --- helpers/QtiXmlLoader.php | 87 ++++++++++++++++ manifest.php | 3 + model/qti/ImportService.php | 12 +++ ...ifierGenerationStrategyServiceProvider.php | 52 ++++++++++ .../UniqueNumericQtiIdentifierReplacer.php | 62 ++++++++++++ test/unit/helpers/QtiXmlLoaderTest.php | 61 ++++++++++++ test/unit/helpers/qtiExamples/invalidQti.xml | 1 + test/unit/helpers/qtiExamples/qti.xml | 22 +++++ ...UniqueNumericQtiIdentifierReplacerTest.php | 98 +++++++++++++++++++ test/unit/model/qti/parser/qti.xml | 22 +++++ 10 files changed, 420 insertions(+) create mode 100644 helpers/QtiXmlLoader.php create mode 100644 model/qti/ServiceProvider/IdentifierGenerationStrategyServiceProvider.php create mode 100644 model/qti/parser/UniqueNumericQtiIdentifierReplacer.php create mode 100644 test/unit/helpers/QtiXmlLoaderTest.php create mode 100644 test/unit/helpers/qtiExamples/invalidQti.xml create mode 100644 test/unit/helpers/qtiExamples/qti.xml create mode 100644 test/unit/model/qti/parser/UniqueNumericQtiIdentifierReplacerTest.php create mode 100644 test/unit/model/qti/parser/qti.xml diff --git a/helpers/QtiXmlLoader.php b/helpers/QtiXmlLoader.php new file mode 100644 index 0000000000..f364f0694f --- /dev/null +++ b/helpers/QtiXmlLoader.php @@ -0,0 +1,87 @@ +extensionsManager = $extensionsManager; + } + + /** + * Load QTI xml and return DOMDocument instance. + * This is service implementation of oat\taoQtiItem\helpers\Authoring::loadQtiXml + * @throws QtiModelException + */ + public function load(string $xml): DOMDocument + { + $dom = new DOMDocument('1.0', 'UTF-8'); + $this->configDomParser($dom); + try { + $dom->loadXML($xml); + } catch (Exception $e) { + throw new QtiModelException('Invalid QTI XML', 0, $e); + } + + return $dom; + } + + private function getQtiParserConfig(): array + { + return $this->extensionsManager->getExtensionById('taoQtiItem') + ->getConfig('XMLParser'); + } + + private function configDomParser(DOMDocument $dom): void + { + $parserConfig = $this->getQtiParserConfig(); + if ($this->parserConfigValid($parserConfig)) { + $dom->formatOutput = $parserConfig['formatOutput']; + $dom->preserveWhiteSpace = $parserConfig['preserveWhiteSpace']; + $dom->validateOnParse = $parserConfig['validateOnParse']; + + return; + } + + $dom->formatOutput = true; + $dom->preserveWhiteSpace = false; + $dom->validateOnParse = false; + } + + private function parserConfigValid(array $parserConfig): bool + { + return !empty($parserConfig) && + isset($parserConfig['formatOutput'], $parserConfig['preserveWhiteSpace'], $parserConfig['validateOnParse']); + } +} diff --git a/manifest.php b/manifest.php index d8c34b3945..98cbd6c385 100755 --- a/manifest.php +++ b/manifest.php @@ -33,6 +33,7 @@ use oat\taoQtiItem\model\FeatureFlag\ServiceProvider\FeatureFlagFlaServiceProvider; use oat\taoQtiItem\model\FeatureFlag\ServiceProvider\FeatureFlagQtiIdentifierServiceProvider; use oat\taoQtiItem\model\qti\metadata\importer\MetaMetadataServiceProvider; +use oat\taoQtiItem\model\qti\ServiceProvider\IdentifierGenerationStrategyServiceProvider; use oat\taoQtiItem\model\qti\ServiceProvider\ItemIdentifierValidatorServiceProvider; use oat\taoQtiItem\model\qti\ServiceProvider\MetadataServiceProvider; use oat\taoQtiItem\scripts\install\InitMetadataService; @@ -204,6 +205,8 @@ ItemIdentifierValidatorServiceProvider::class, MetadataServiceProvider::class, MetaMetadataServiceProvider::class, + IdentifierGenerationStrategyServiceProvider::class, + MetaMetadataServiceProvider::class, FeatureFlagQtiIdentifierServiceProvider::class, ], ]; diff --git a/model/qti/ImportService.php b/model/qti/ImportService.php index 90d7f739c4..f58cbb2d1f 100755 --- a/model/qti/ImportService.php +++ b/model/qti/ImportService.php @@ -54,6 +54,7 @@ use oat\taoQtiItem\model\qti\metadata\MetadataGuardianResource; use oat\taoQtiItem\model\qti\metadata\MetadataService; use oat\taoQtiItem\model\qti\metadata\ontology\MappedMetadataInjector; +use oat\taoQtiItem\model\qti\parser\UniqueNumericQtiIdentifierReplacer; use oat\taoQtiItem\model\qti\parser\ValidationException; use oat\taoQtiItem\model\event\ItemImported; use qtism\data\QtiComponentCollection; @@ -187,6 +188,7 @@ protected function createRdfItem(core_kernel_classes_Class $itemClass, Item $qti protected function createQtiItemModel($qtiFile, $validate = true) { $qtiXml = Authoring::sanitizeQtiXml($qtiFile); + $qtiXml = $this->replaceUniqueNumericQtiIdentifier($qtiXml); //validate the file to import $qtiParser = new Parser($qtiXml); @@ -952,4 +954,14 @@ private function getMetaMetadataImportMapper(): MetaMetadataImportMapper { return $this->getServiceManager()->getContainer()->get(MetaMetadataImportMapper::class); } + + private function getUniqueNumericQtiIdentifierReplacer(): UniqueNumericQtiIdentifierReplacer + { + return $this->getServiceManager()->getContainer()->get(UniqueNumericQtiIdentifierReplacer::class); + } + + private function replaceUniqueNumericQtiIdentifier(string $qtiXml) + { + return $this->getUniqueNumericQtiIdentifierReplacer()->replace($qtiXml); + } } diff --git a/model/qti/ServiceProvider/IdentifierGenerationStrategyServiceProvider.php b/model/qti/ServiceProvider/IdentifierGenerationStrategyServiceProvider.php new file mode 100644 index 0000000000..638ac30e09 --- /dev/null +++ b/model/qti/ServiceProvider/IdentifierGenerationStrategyServiceProvider.php @@ -0,0 +1,52 @@ +services(); + + $services->set(QtiXmlLoader::class, QtiXmlLoader::class) + ->args([ + service(ExtensionsManager::SERVICE_ID) + ]); + + $services->set(UniqueNumericQtiIdentifierReplacer::class, UniqueNumericQtiIdentifierReplacer::class) + ->args([ + service(FeatureFlagChecker::class), + service(QtiXmlLoader::class) + ]) + ->public(); + } +} diff --git a/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php b/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php new file mode 100644 index 0000000000..a92873b780 --- /dev/null +++ b/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php @@ -0,0 +1,62 @@ +featureFlagChecker = $featureFlagChecker; + $this->qtiXmlLoader = $qtiXmlLoader; + } + public function replace(string $qti): string + { + if (!$this->featureFlagChecker->isEnabled(FeatureFlagChecker::FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER)) { + return $qti; + } + $doc = $this->qtiXmlLoader->load($qti); + + $xpath = new DOMXpath($doc); + $xpath->registerNamespace('qti', 'http://www.imsglobal.org/xsd/imsqti_v2p2'); + $identifierNodes = $xpath->query('//qti:assessmentItem/@identifier'); + + foreach ($identifierNodes as $identifierNode) { + $identifierNode->nodeValue = $this->getNumericIdentifier(); + } + + return $doc->saveXML(); + } + + private function getNumericIdentifier(): string + { + return substr((string)floor(time() / 1000), 0, 7) + . substr((string)floor(mt_rand(10, 100)), 0, 2); + } +} diff --git a/test/unit/helpers/QtiXmlLoaderTest.php b/test/unit/helpers/QtiXmlLoaderTest.php new file mode 100644 index 0000000000..dfe1847f1a --- /dev/null +++ b/test/unit/helpers/QtiXmlLoaderTest.php @@ -0,0 +1,61 @@ +extensionsManagerMock = $this->createMock(ExtensionsManager::class); + $this->extensionMock = $this->createMock(Extension::class); + $this->extensionsManagerMock->method('getExtensionById')->willReturn($this->extensionMock); + $this->extensionMock->method('getConfig')->willReturn([ + 'formatOutput' => true, + 'preserveWhiteSpace' => false, + 'validateOnParse' => false, + ]); + $this->subject = new QtiXmlLoader($this->extensionsManagerMock); + } + + public function testLoad() + { + $qti = file_get_contents(__DIR__ . '/qtiExamples/qti.xml'); + $sub = $this->subject->load($qti); + + $this->assertInstanceOf(DOMDocument::class, $sub); + } + + public function testInvalidLoad() + { + $this->expectException(QtiModelException::class); + $qti = file_get_contents(__DIR__ . '/qtiExamples/invalidQti.xml'); + $this->subject->load($qti); + } +} diff --git a/test/unit/helpers/qtiExamples/invalidQti.xml b/test/unit/helpers/qtiExamples/invalidQti.xml new file mode 100644 index 0000000000..0c96779aba --- /dev/null +++ b/test/unit/helpers/qtiExamples/invalidQti.xml @@ -0,0 +1 @@ +This cannot be loaded diff --git a/test/unit/helpers/qtiExamples/qti.xml b/test/unit/helpers/qtiExamples/qti.xml new file mode 100644 index 0000000000..e242179204 --- /dev/null +++ b/test/unit/helpers/qtiExamples/qti.xml @@ -0,0 +1,22 @@ + + + + + + + 0 + + + +
+
+ + choice #1 + choice #2 + choice #3 + +
+
+
+ +
diff --git a/test/unit/model/qti/parser/UniqueNumericQtiIdentifierReplacerTest.php b/test/unit/model/qti/parser/UniqueNumericQtiIdentifierReplacerTest.php new file mode 100644 index 0000000000..c9f55ed9a7 --- /dev/null +++ b/test/unit/model/qti/parser/UniqueNumericQtiIdentifierReplacerTest.php @@ -0,0 +1,98 @@ +featureFlagCheckerMock = $this->createMock(FeatureFlagChecker::class); + $this->qtiXmlLoaderMock = $this->createMock(QtiXmlLoader::class); + + $this->subject = new UniqueNumericQtiIdentifierReplacer( + $this->featureFlagCheckerMock, + $this->qtiXmlLoaderMock + ); + } + + public function testReplace(): void + { + + $qti = file_get_contents(__DIR__ . '/qti.xml'); + $this->featureFlagCheckerMock->method('isEnabled') + ->willReturn(true); + + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->loadXML($qti); + + $xpath = new DOMXpath($dom); + $xpath->registerNamespace('qti', 'http://www.imsglobal.org/xsd/imsqti_v2p2'); + $identifierNodes = $xpath->query('//qti:assessmentItem/@identifier'); + $initialValue = $identifierNodes->item(0)->nodeValue; + + $this->qtiXmlLoaderMock->method('load') + ->willReturn($dom); + + $this->subject->replace($qti); + + $xpath = new DOMXpath($dom); + $xpath->registerNamespace('qti', 'http://www.imsglobal.org/xsd/imsqti_v2p2'); + $identifierNodes = $xpath->query('//qti:assessmentItem/@identifier'); + $this->assertRegExp('/\d{9}/', $identifierNodes->item(0)->nodeValue); + $this->assertNotEquals($initialValue, $identifierNodes->item(0)->nodeValue); + } + + public function testReplaceWhenFeatureFlagDisabled(): void + { + + $qti = file_get_contents(__DIR__ . '/qti.xml'); + $this->featureFlagCheckerMock->method('isEnabled') + ->willReturn(false); + + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->loadXML($qti); + + $xpath = new DOMXpath($dom); + $xpath->registerNamespace('qti', 'http://www.imsglobal.org/xsd/imsqti_v2p2'); + $identifierNodes = $xpath->query('//qti:assessmentItem/@identifier'); + $initialValue = $identifierNodes->item(0)->nodeValue; + + $this->qtiXmlLoaderMock->method('load') + ->willReturn($dom); + + $this->subject->replace($qti); + + $xpath = new DOMXpath($dom); + $xpath->registerNamespace('qti', 'http://www.imsglobal.org/xsd/imsqti_v2p2'); + $identifierNodes = $xpath->query('//qti:assessmentItem/@identifier'); + $this->assertEquals($initialValue, $identifierNodes->item(0)->nodeValue); + } +} diff --git a/test/unit/model/qti/parser/qti.xml b/test/unit/model/qti/parser/qti.xml new file mode 100644 index 0000000000..e242179204 --- /dev/null +++ b/test/unit/model/qti/parser/qti.xml @@ -0,0 +1,22 @@ + + + + + + + 0 + + + +
+
+ + choice #1 + choice #2 + choice #3 + +
+
+
+ +
From b712a662a98b7c260abe21f1d83024db820284c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 09:53:25 +0200 Subject: [PATCH 15/41] feat: temp fix composer --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4938a574c0..ab7b3301fe 100644 --- a/composer.json +++ b/composer.json @@ -66,7 +66,7 @@ "oat-sa/oatbox-extension-installer": "~1.1||dev-master", "naneau/semver": "~0.0.7", "oat-sa/generis": ">=15.36.4", - "oat-sa/tao-core": ">=54.14.7", + "oat-sa/tao-core": "dev-feat/AUT-3705/uuid-qti-identifier as 54.14.7", "oat-sa/extension-tao-item": ">=12.0.0", "oat-sa/extension-tao-test": ">=16.0.0" }, From 81e2ddf5e4d86b742b919c97da0340b4945ab000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 15:37:08 +0200 Subject: [PATCH 16/41] feat: disable identifier input field --- .../FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php | 1 + views/js/qtiCreator/tpl/forms/item.tpl | 1 + views/js/qtiCreator/widgets/helpers/qtiIdentifier.js | 4 +++- views/js/qtiCreator/widgets/item/states/Active.js | 7 +++++-- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php b/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php index 8cebfcb990..3d41271a37 100644 --- a/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php +++ b/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php @@ -37,6 +37,7 @@ public function __invoke(array $configs): array $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['qtiIdPattern'] = '/^\\d{9}$/'; $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['invalidQtiIdMessage'] = 'The QTI identifier must be a 9-digit number.'; + $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['isDisabled'] = true; } return $configs; diff --git a/views/js/qtiCreator/tpl/forms/item.tpl b/views/js/qtiCreator/tpl/forms/item.tpl index 0834c36ef0..ff5a34fb23 100644 --- a/views/js/qtiCreator/tpl/forms/item.tpl +++ b/views/js/qtiCreator/tpl/forms/item.tpl @@ -7,6 +7,7 @@ diff --git a/views/js/qtiCreator/widgets/helpers/qtiIdentifier.js b/views/js/qtiCreator/widgets/helpers/qtiIdentifier.js index 47beb445df..f63c7f244c 100644 --- a/views/js/qtiCreator/widgets/helpers/qtiIdentifier.js +++ b/views/js/qtiCreator/widgets/helpers/qtiIdentifier.js @@ -24,10 +24,12 @@ define(['module', 'i18n'], function (module, __) { const defaultInvalidQtiIdMessage = 'Identifiers must start with a letter or an underscore and contain only letters, numbers, dots, underscores ( _ ), or hyphens ( - ).'; const message = module.config().invalidQtiIdMessage || defaultInvalidQtiIdMessage; const invalidQtiIdMessage = __(message); + const isDisabled = module.config().isDisabled || false; return { pattern: new RegExp(patternContent, flags), invalidQtiIdMessage, - maxQtiIdLength: 32 + maxQtiIdLength: 32, + isDisabled }; }); diff --git a/views/js/qtiCreator/widgets/item/states/Active.js b/views/js/qtiCreator/widgets/item/states/Active.js index 610eab8adc..7534e7386d 100755 --- a/views/js/qtiCreator/widgets/item/states/Active.js +++ b/views/js/qtiCreator/widgets/item/states/Active.js @@ -24,8 +24,9 @@ define([ 'taoQtiItem/qtiCreator/widgets/states/Active', 'tpl!taoQtiItem/qtiCreator/tpl/forms/item', 'taoQtiItem/qtiCreator/widgets/helpers/formElement', + 'taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier', 'select2' -], function (_, features, languages, stateFactory, Active, formTpl, formElement ) { +], function (_, features, languages, stateFactory, Active, formTpl, formElement, qtiIdentifier) { 'use strict'; const ItemStateActive = stateFactory.create( @@ -38,6 +39,7 @@ define([ const $itemBody = _widget.$container.find('.qti-itemBody'); const showIdentifier = features.isVisible('taoQtiItem/creator/item/property/identifier'); + const disableIdentifier = qtiIdentifier.isDisabled //build form: $form.html( @@ -49,7 +51,8 @@ define([ timeDependent: !!item.attr('timeDependent'), showTimeDependent: features.isVisible('taoQtiItem/creator/item/property/timeDependant'), 'xml:lang': item.attr('xml:lang'), - languagesList: item.data('languagesList') + languagesList: item.data('languagesList'), + disableIdentifier }) ); From 86c62b17dde67515f26404889a53d57de7b829ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 16:17:39 +0200 Subject: [PATCH 17/41] feat: revert composer changes --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ab7b3301fe..4938a574c0 100644 --- a/composer.json +++ b/composer.json @@ -66,7 +66,7 @@ "oat-sa/oatbox-extension-installer": "~1.1||dev-master", "naneau/semver": "~0.0.7", "oat-sa/generis": ">=15.36.4", - "oat-sa/tao-core": "dev-feat/AUT-3705/uuid-qti-identifier as 54.14.7", + "oat-sa/tao-core": ">=54.14.7", "oat-sa/extension-tao-item": ">=12.0.0", "oat-sa/extension-tao-test": ">=16.0.0" }, From b04fd5c974ef84d0c42cc9e73a45022d4bd62c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Thu, 27 Jun 2024 08:51:43 +0200 Subject: [PATCH 18/41] feat: services used as handlers should be public --- .../FeatureFlagQtiIdentifierServiceProvider.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/model/FeatureFlag/ServiceProvider/FeatureFlagQtiIdentifierServiceProvider.php b/model/FeatureFlag/ServiceProvider/FeatureFlagQtiIdentifierServiceProvider.php index 36541b8840..59794a49e2 100644 --- a/model/FeatureFlag/ServiceProvider/FeatureFlagQtiIdentifierServiceProvider.php +++ b/model/FeatureFlag/ServiceProvider/FeatureFlagQtiIdentifierServiceProvider.php @@ -25,8 +25,6 @@ use oat\generis\model\DependencyInjection\ContainerServiceProviderInterface; use oat\tao\model\featureFlag\FeatureFlagChecker; use oat\tao\model\featureFlag\FeatureFlagConfigSwitcher; -use oat\taoDeliverConnect\model\FeatureFlag\FeatureFlagClientConfigHandler; -use oat\taoDeliverConnect\model\FeatureFlag\FeatureFlagQtiCreatorConfigHandler; use oat\taoQtiItem\model\FeatureFlag\UniqueNumericQtiIdentifierClientConfig; use oat\taoQtiItem\model\FeatureFlag\UniqueNumericQtiIdentifierQtiCreator; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; @@ -41,12 +39,21 @@ public function __invoke(ContainerConfigurator $configurator): void $services ->set(UniqueNumericQtiIdentifierClientConfig::class) - ->public() ->args( [ service(FeatureFlagChecker::class), ] - ); + ) + ->public(); + + $services + ->set(UniqueNumericQtiIdentifierQtiCreator::class) + ->args( + [ + service(FeatureFlagChecker::class), + ] + ) + ->public(); $services->get(FeatureFlagConfigSwitcher::class) ->call( From 809fcea4cb619dd4cc5ac4a5baada39f000069c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Thu, 27 Jun 2024 08:54:58 +0200 Subject: [PATCH 19/41] feat: remove ff definition from const --- .../FeatureFlag/UniqueNumericFeatureFlag.php | 28 ------------------- ...UniqueNumericQtiIdentifierClientConfig.php | 2 +- .../UniqueNumericQtiIdentifierQtiCreator.php | 2 +- 3 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 model/FeatureFlag/UniqueNumericFeatureFlag.php diff --git a/model/FeatureFlag/UniqueNumericFeatureFlag.php b/model/FeatureFlag/UniqueNumericFeatureFlag.php deleted file mode 100644 index 3fee412e0b..0000000000 --- a/model/FeatureFlag/UniqueNumericFeatureFlag.php +++ /dev/null @@ -1,28 +0,0 @@ -featureFlagChecker - ->isEnabled(UniqueNumericFeatureFlag::FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER); + ->isEnabled('FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER'); } } diff --git a/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php b/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php index c2ffb7533a..c9ac7de6e5 100644 --- a/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php +++ b/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php @@ -46,6 +46,6 @@ public function __invoke(array $configs): array private function isEnabled(): bool { return $this->featureFlagChecker - ->isEnabled(UniqueNumericFeatureFlag::FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER); + ->isEnabled('FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER'); } } From a7c9ccc0ebc4fdfc43e54d7bd5e13606f0081103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Thu, 27 Jun 2024 08:54:58 +0200 Subject: [PATCH 20/41] feat: remove ff definition from const --- .../FeatureFlag/UniqueNumericFeatureFlag.php | 28 ------------------- ...UniqueNumericQtiIdentifierClientConfig.php | 2 +- .../UniqueNumericQtiIdentifierQtiCreator.php | 2 +- 3 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 model/FeatureFlag/UniqueNumericFeatureFlag.php diff --git a/model/FeatureFlag/UniqueNumericFeatureFlag.php b/model/FeatureFlag/UniqueNumericFeatureFlag.php deleted file mode 100644 index 3fee412e0b..0000000000 --- a/model/FeatureFlag/UniqueNumericFeatureFlag.php +++ /dev/null @@ -1,28 +0,0 @@ -featureFlagChecker - ->isEnabled(UniqueNumericFeatureFlag::FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER); + ->isEnabled('FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER'); } } diff --git a/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php b/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php index c2ffb7533a..c9ac7de6e5 100644 --- a/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php +++ b/model/FeatureFlag/UniqueNumericQtiIdentifierQtiCreator.php @@ -46,6 +46,6 @@ public function __invoke(array $configs): array private function isEnabled(): bool { return $this->featureFlagChecker - ->isEnabled(UniqueNumericFeatureFlag::FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER); + ->isEnabled('FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER'); } } From 4ae0bdde505beb8d260cb7a6a236a60559fe5ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Thu, 27 Jun 2024 08:51:43 +0200 Subject: [PATCH 21/41] feat: services used as handlers should be public --- .../FeatureFlagQtiIdentifierServiceProvider.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/model/FeatureFlag/ServiceProvider/FeatureFlagQtiIdentifierServiceProvider.php b/model/FeatureFlag/ServiceProvider/FeatureFlagQtiIdentifierServiceProvider.php index 36541b8840..59794a49e2 100644 --- a/model/FeatureFlag/ServiceProvider/FeatureFlagQtiIdentifierServiceProvider.php +++ b/model/FeatureFlag/ServiceProvider/FeatureFlagQtiIdentifierServiceProvider.php @@ -25,8 +25,6 @@ use oat\generis\model\DependencyInjection\ContainerServiceProviderInterface; use oat\tao\model\featureFlag\FeatureFlagChecker; use oat\tao\model\featureFlag\FeatureFlagConfigSwitcher; -use oat\taoDeliverConnect\model\FeatureFlag\FeatureFlagClientConfigHandler; -use oat\taoDeliverConnect\model\FeatureFlag\FeatureFlagQtiCreatorConfigHandler; use oat\taoQtiItem\model\FeatureFlag\UniqueNumericQtiIdentifierClientConfig; use oat\taoQtiItem\model\FeatureFlag\UniqueNumericQtiIdentifierQtiCreator; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; @@ -41,12 +39,21 @@ public function __invoke(ContainerConfigurator $configurator): void $services ->set(UniqueNumericQtiIdentifierClientConfig::class) - ->public() ->args( [ service(FeatureFlagChecker::class), ] - ); + ) + ->public(); + + $services + ->set(UniqueNumericQtiIdentifierQtiCreator::class) + ->args( + [ + service(FeatureFlagChecker::class), + ] + ) + ->public(); $services->get(FeatureFlagConfigSwitcher::class) ->call( From c1bb18921e35bba09a5828591dbaa9e372a14aac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 09:30:52 +0200 Subject: [PATCH 22/41] feat: When importing item with UniqueNumeric we will regenerate identifier --- helpers/QtiXmlLoader.php | 87 ++++++++++++++++ manifest.php | 3 + model/qti/ImportService.php | 12 +++ ...ifierGenerationStrategyServiceProvider.php | 52 ++++++++++ .../UniqueNumericQtiIdentifierReplacer.php | 62 ++++++++++++ test/unit/helpers/QtiXmlLoaderTest.php | 61 ++++++++++++ test/unit/helpers/qtiExamples/invalidQti.xml | 1 + test/unit/helpers/qtiExamples/qti.xml | 22 +++++ ...UniqueNumericQtiIdentifierReplacerTest.php | 98 +++++++++++++++++++ test/unit/model/qti/parser/qti.xml | 22 +++++ 10 files changed, 420 insertions(+) create mode 100644 helpers/QtiXmlLoader.php create mode 100644 model/qti/ServiceProvider/IdentifierGenerationStrategyServiceProvider.php create mode 100644 model/qti/parser/UniqueNumericQtiIdentifierReplacer.php create mode 100644 test/unit/helpers/QtiXmlLoaderTest.php create mode 100644 test/unit/helpers/qtiExamples/invalidQti.xml create mode 100644 test/unit/helpers/qtiExamples/qti.xml create mode 100644 test/unit/model/qti/parser/UniqueNumericQtiIdentifierReplacerTest.php create mode 100644 test/unit/model/qti/parser/qti.xml diff --git a/helpers/QtiXmlLoader.php b/helpers/QtiXmlLoader.php new file mode 100644 index 0000000000..f364f0694f --- /dev/null +++ b/helpers/QtiXmlLoader.php @@ -0,0 +1,87 @@ +extensionsManager = $extensionsManager; + } + + /** + * Load QTI xml and return DOMDocument instance. + * This is service implementation of oat\taoQtiItem\helpers\Authoring::loadQtiXml + * @throws QtiModelException + */ + public function load(string $xml): DOMDocument + { + $dom = new DOMDocument('1.0', 'UTF-8'); + $this->configDomParser($dom); + try { + $dom->loadXML($xml); + } catch (Exception $e) { + throw new QtiModelException('Invalid QTI XML', 0, $e); + } + + return $dom; + } + + private function getQtiParserConfig(): array + { + return $this->extensionsManager->getExtensionById('taoQtiItem') + ->getConfig('XMLParser'); + } + + private function configDomParser(DOMDocument $dom): void + { + $parserConfig = $this->getQtiParserConfig(); + if ($this->parserConfigValid($parserConfig)) { + $dom->formatOutput = $parserConfig['formatOutput']; + $dom->preserveWhiteSpace = $parserConfig['preserveWhiteSpace']; + $dom->validateOnParse = $parserConfig['validateOnParse']; + + return; + } + + $dom->formatOutput = true; + $dom->preserveWhiteSpace = false; + $dom->validateOnParse = false; + } + + private function parserConfigValid(array $parserConfig): bool + { + return !empty($parserConfig) && + isset($parserConfig['formatOutput'], $parserConfig['preserveWhiteSpace'], $parserConfig['validateOnParse']); + } +} diff --git a/manifest.php b/manifest.php index d8c34b3945..98cbd6c385 100755 --- a/manifest.php +++ b/manifest.php @@ -33,6 +33,7 @@ use oat\taoQtiItem\model\FeatureFlag\ServiceProvider\FeatureFlagFlaServiceProvider; use oat\taoQtiItem\model\FeatureFlag\ServiceProvider\FeatureFlagQtiIdentifierServiceProvider; use oat\taoQtiItem\model\qti\metadata\importer\MetaMetadataServiceProvider; +use oat\taoQtiItem\model\qti\ServiceProvider\IdentifierGenerationStrategyServiceProvider; use oat\taoQtiItem\model\qti\ServiceProvider\ItemIdentifierValidatorServiceProvider; use oat\taoQtiItem\model\qti\ServiceProvider\MetadataServiceProvider; use oat\taoQtiItem\scripts\install\InitMetadataService; @@ -204,6 +205,8 @@ ItemIdentifierValidatorServiceProvider::class, MetadataServiceProvider::class, MetaMetadataServiceProvider::class, + IdentifierGenerationStrategyServiceProvider::class, + MetaMetadataServiceProvider::class, FeatureFlagQtiIdentifierServiceProvider::class, ], ]; diff --git a/model/qti/ImportService.php b/model/qti/ImportService.php index 90d7f739c4..f58cbb2d1f 100755 --- a/model/qti/ImportService.php +++ b/model/qti/ImportService.php @@ -54,6 +54,7 @@ use oat\taoQtiItem\model\qti\metadata\MetadataGuardianResource; use oat\taoQtiItem\model\qti\metadata\MetadataService; use oat\taoQtiItem\model\qti\metadata\ontology\MappedMetadataInjector; +use oat\taoQtiItem\model\qti\parser\UniqueNumericQtiIdentifierReplacer; use oat\taoQtiItem\model\qti\parser\ValidationException; use oat\taoQtiItem\model\event\ItemImported; use qtism\data\QtiComponentCollection; @@ -187,6 +188,7 @@ protected function createRdfItem(core_kernel_classes_Class $itemClass, Item $qti protected function createQtiItemModel($qtiFile, $validate = true) { $qtiXml = Authoring::sanitizeQtiXml($qtiFile); + $qtiXml = $this->replaceUniqueNumericQtiIdentifier($qtiXml); //validate the file to import $qtiParser = new Parser($qtiXml); @@ -952,4 +954,14 @@ private function getMetaMetadataImportMapper(): MetaMetadataImportMapper { return $this->getServiceManager()->getContainer()->get(MetaMetadataImportMapper::class); } + + private function getUniqueNumericQtiIdentifierReplacer(): UniqueNumericQtiIdentifierReplacer + { + return $this->getServiceManager()->getContainer()->get(UniqueNumericQtiIdentifierReplacer::class); + } + + private function replaceUniqueNumericQtiIdentifier(string $qtiXml) + { + return $this->getUniqueNumericQtiIdentifierReplacer()->replace($qtiXml); + } } diff --git a/model/qti/ServiceProvider/IdentifierGenerationStrategyServiceProvider.php b/model/qti/ServiceProvider/IdentifierGenerationStrategyServiceProvider.php new file mode 100644 index 0000000000..638ac30e09 --- /dev/null +++ b/model/qti/ServiceProvider/IdentifierGenerationStrategyServiceProvider.php @@ -0,0 +1,52 @@ +services(); + + $services->set(QtiXmlLoader::class, QtiXmlLoader::class) + ->args([ + service(ExtensionsManager::SERVICE_ID) + ]); + + $services->set(UniqueNumericQtiIdentifierReplacer::class, UniqueNumericQtiIdentifierReplacer::class) + ->args([ + service(FeatureFlagChecker::class), + service(QtiXmlLoader::class) + ]) + ->public(); + } +} diff --git a/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php b/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php new file mode 100644 index 0000000000..a92873b780 --- /dev/null +++ b/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php @@ -0,0 +1,62 @@ +featureFlagChecker = $featureFlagChecker; + $this->qtiXmlLoader = $qtiXmlLoader; + } + public function replace(string $qti): string + { + if (!$this->featureFlagChecker->isEnabled(FeatureFlagChecker::FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER)) { + return $qti; + } + $doc = $this->qtiXmlLoader->load($qti); + + $xpath = new DOMXpath($doc); + $xpath->registerNamespace('qti', 'http://www.imsglobal.org/xsd/imsqti_v2p2'); + $identifierNodes = $xpath->query('//qti:assessmentItem/@identifier'); + + foreach ($identifierNodes as $identifierNode) { + $identifierNode->nodeValue = $this->getNumericIdentifier(); + } + + return $doc->saveXML(); + } + + private function getNumericIdentifier(): string + { + return substr((string)floor(time() / 1000), 0, 7) + . substr((string)floor(mt_rand(10, 100)), 0, 2); + } +} diff --git a/test/unit/helpers/QtiXmlLoaderTest.php b/test/unit/helpers/QtiXmlLoaderTest.php new file mode 100644 index 0000000000..dfe1847f1a --- /dev/null +++ b/test/unit/helpers/QtiXmlLoaderTest.php @@ -0,0 +1,61 @@ +extensionsManagerMock = $this->createMock(ExtensionsManager::class); + $this->extensionMock = $this->createMock(Extension::class); + $this->extensionsManagerMock->method('getExtensionById')->willReturn($this->extensionMock); + $this->extensionMock->method('getConfig')->willReturn([ + 'formatOutput' => true, + 'preserveWhiteSpace' => false, + 'validateOnParse' => false, + ]); + $this->subject = new QtiXmlLoader($this->extensionsManagerMock); + } + + public function testLoad() + { + $qti = file_get_contents(__DIR__ . '/qtiExamples/qti.xml'); + $sub = $this->subject->load($qti); + + $this->assertInstanceOf(DOMDocument::class, $sub); + } + + public function testInvalidLoad() + { + $this->expectException(QtiModelException::class); + $qti = file_get_contents(__DIR__ . '/qtiExamples/invalidQti.xml'); + $this->subject->load($qti); + } +} diff --git a/test/unit/helpers/qtiExamples/invalidQti.xml b/test/unit/helpers/qtiExamples/invalidQti.xml new file mode 100644 index 0000000000..0c96779aba --- /dev/null +++ b/test/unit/helpers/qtiExamples/invalidQti.xml @@ -0,0 +1 @@ +This cannot be loaded diff --git a/test/unit/helpers/qtiExamples/qti.xml b/test/unit/helpers/qtiExamples/qti.xml new file mode 100644 index 0000000000..e242179204 --- /dev/null +++ b/test/unit/helpers/qtiExamples/qti.xml @@ -0,0 +1,22 @@ + + + + + + + 0 + + + +
+
+ + choice #1 + choice #2 + choice #3 + +
+
+
+ +
diff --git a/test/unit/model/qti/parser/UniqueNumericQtiIdentifierReplacerTest.php b/test/unit/model/qti/parser/UniqueNumericQtiIdentifierReplacerTest.php new file mode 100644 index 0000000000..c9f55ed9a7 --- /dev/null +++ b/test/unit/model/qti/parser/UniqueNumericQtiIdentifierReplacerTest.php @@ -0,0 +1,98 @@ +featureFlagCheckerMock = $this->createMock(FeatureFlagChecker::class); + $this->qtiXmlLoaderMock = $this->createMock(QtiXmlLoader::class); + + $this->subject = new UniqueNumericQtiIdentifierReplacer( + $this->featureFlagCheckerMock, + $this->qtiXmlLoaderMock + ); + } + + public function testReplace(): void + { + + $qti = file_get_contents(__DIR__ . '/qti.xml'); + $this->featureFlagCheckerMock->method('isEnabled') + ->willReturn(true); + + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->loadXML($qti); + + $xpath = new DOMXpath($dom); + $xpath->registerNamespace('qti', 'http://www.imsglobal.org/xsd/imsqti_v2p2'); + $identifierNodes = $xpath->query('//qti:assessmentItem/@identifier'); + $initialValue = $identifierNodes->item(0)->nodeValue; + + $this->qtiXmlLoaderMock->method('load') + ->willReturn($dom); + + $this->subject->replace($qti); + + $xpath = new DOMXpath($dom); + $xpath->registerNamespace('qti', 'http://www.imsglobal.org/xsd/imsqti_v2p2'); + $identifierNodes = $xpath->query('//qti:assessmentItem/@identifier'); + $this->assertRegExp('/\d{9}/', $identifierNodes->item(0)->nodeValue); + $this->assertNotEquals($initialValue, $identifierNodes->item(0)->nodeValue); + } + + public function testReplaceWhenFeatureFlagDisabled(): void + { + + $qti = file_get_contents(__DIR__ . '/qti.xml'); + $this->featureFlagCheckerMock->method('isEnabled') + ->willReturn(false); + + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->loadXML($qti); + + $xpath = new DOMXpath($dom); + $xpath->registerNamespace('qti', 'http://www.imsglobal.org/xsd/imsqti_v2p2'); + $identifierNodes = $xpath->query('//qti:assessmentItem/@identifier'); + $initialValue = $identifierNodes->item(0)->nodeValue; + + $this->qtiXmlLoaderMock->method('load') + ->willReturn($dom); + + $this->subject->replace($qti); + + $xpath = new DOMXpath($dom); + $xpath->registerNamespace('qti', 'http://www.imsglobal.org/xsd/imsqti_v2p2'); + $identifierNodes = $xpath->query('//qti:assessmentItem/@identifier'); + $this->assertEquals($initialValue, $identifierNodes->item(0)->nodeValue); + } +} diff --git a/test/unit/model/qti/parser/qti.xml b/test/unit/model/qti/parser/qti.xml new file mode 100644 index 0000000000..e242179204 --- /dev/null +++ b/test/unit/model/qti/parser/qti.xml @@ -0,0 +1,22 @@ + + + + + + + 0 + + + +
+
+ + choice #1 + choice #2 + choice #3 + +
+
+
+ +
From ff06ebf3604330280f287a5f041d2966f530a0fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 09:53:25 +0200 Subject: [PATCH 23/41] feat: temp fix composer --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4938a574c0..ab7b3301fe 100644 --- a/composer.json +++ b/composer.json @@ -66,7 +66,7 @@ "oat-sa/oatbox-extension-installer": "~1.1||dev-master", "naneau/semver": "~0.0.7", "oat-sa/generis": ">=15.36.4", - "oat-sa/tao-core": ">=54.14.7", + "oat-sa/tao-core": "dev-feat/AUT-3705/uuid-qti-identifier as 54.14.7", "oat-sa/extension-tao-item": ">=12.0.0", "oat-sa/extension-tao-test": ">=16.0.0" }, From f15336c20be1b7064e1882fd6a6444afef91f0d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 09:30:52 +0200 Subject: [PATCH 24/41] feat: When importing item with UniqueNumeric we will regenerate identifier --- manifest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/manifest.php b/manifest.php index 98cbd6c385..ef4331f6b1 100755 --- a/manifest.php +++ b/manifest.php @@ -206,7 +206,6 @@ MetadataServiceProvider::class, MetaMetadataServiceProvider::class, IdentifierGenerationStrategyServiceProvider::class, - MetaMetadataServiceProvider::class, FeatureFlagQtiIdentifierServiceProvider::class, ], ]; From d344a184117517f87a2692da7dc91d008cda1dea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Thu, 27 Jun 2024 10:03:03 +0200 Subject: [PATCH 25/41] feat: revert composer json change --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ab7b3301fe..4938a574c0 100644 --- a/composer.json +++ b/composer.json @@ -66,7 +66,7 @@ "oat-sa/oatbox-extension-installer": "~1.1||dev-master", "naneau/semver": "~0.0.7", "oat-sa/generis": ">=15.36.4", - "oat-sa/tao-core": "dev-feat/AUT-3705/uuid-qti-identifier as 54.14.7", + "oat-sa/tao-core": ">=54.14.7", "oat-sa/extension-tao-item": ">=12.0.0", "oat-sa/extension-tao-test": ">=16.0.0" }, From a63dc397cfa0f7c26da51dbea146af5e64a53564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 26 Jun 2024 15:37:08 +0200 Subject: [PATCH 26/41] feat: disable identifier input field --- .../FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php | 1 + views/js/qtiCreator/tpl/forms/item.tpl | 1 + views/js/qtiCreator/widgets/helpers/qtiIdentifier.js | 4 +++- views/js/qtiCreator/widgets/item/states/Active.js | 7 +++++-- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php b/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php index 9b325aa808..e2228775d1 100644 --- a/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php +++ b/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php @@ -37,6 +37,7 @@ public function __invoke(array $configs): array $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['qtiIdPattern'] = '/^\\d{9}$/'; $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['invalidQtiIdMessage'] = 'The QTI identifier must be a 9-digit number.'; + $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['isDisabled'] = true; } return $configs; diff --git a/views/js/qtiCreator/tpl/forms/item.tpl b/views/js/qtiCreator/tpl/forms/item.tpl index 0834c36ef0..ff5a34fb23 100644 --- a/views/js/qtiCreator/tpl/forms/item.tpl +++ b/views/js/qtiCreator/tpl/forms/item.tpl @@ -7,6 +7,7 @@ diff --git a/views/js/qtiCreator/widgets/helpers/qtiIdentifier.js b/views/js/qtiCreator/widgets/helpers/qtiIdentifier.js index 47beb445df..f63c7f244c 100644 --- a/views/js/qtiCreator/widgets/helpers/qtiIdentifier.js +++ b/views/js/qtiCreator/widgets/helpers/qtiIdentifier.js @@ -24,10 +24,12 @@ define(['module', 'i18n'], function (module, __) { const defaultInvalidQtiIdMessage = 'Identifiers must start with a letter or an underscore and contain only letters, numbers, dots, underscores ( _ ), or hyphens ( - ).'; const message = module.config().invalidQtiIdMessage || defaultInvalidQtiIdMessage; const invalidQtiIdMessage = __(message); + const isDisabled = module.config().isDisabled || false; return { pattern: new RegExp(patternContent, flags), invalidQtiIdMessage, - maxQtiIdLength: 32 + maxQtiIdLength: 32, + isDisabled }; }); diff --git a/views/js/qtiCreator/widgets/item/states/Active.js b/views/js/qtiCreator/widgets/item/states/Active.js index 610eab8adc..7534e7386d 100755 --- a/views/js/qtiCreator/widgets/item/states/Active.js +++ b/views/js/qtiCreator/widgets/item/states/Active.js @@ -24,8 +24,9 @@ define([ 'taoQtiItem/qtiCreator/widgets/states/Active', 'tpl!taoQtiItem/qtiCreator/tpl/forms/item', 'taoQtiItem/qtiCreator/widgets/helpers/formElement', + 'taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier', 'select2' -], function (_, features, languages, stateFactory, Active, formTpl, formElement ) { +], function (_, features, languages, stateFactory, Active, formTpl, formElement, qtiIdentifier) { 'use strict'; const ItemStateActive = stateFactory.create( @@ -38,6 +39,7 @@ define([ const $itemBody = _widget.$container.find('.qti-itemBody'); const showIdentifier = features.isVisible('taoQtiItem/creator/item/property/identifier'); + const disableIdentifier = qtiIdentifier.isDisabled //build form: $form.html( @@ -49,7 +51,8 @@ define([ timeDependent: !!item.attr('timeDependent'), showTimeDependent: features.isVisible('taoQtiItem/creator/item/property/timeDependant'), 'xml:lang': item.attr('xml:lang'), - languagesList: item.data('languagesList') + languagesList: item.data('languagesList'), + disableIdentifier }) ); From 33ef947bb37f83d545f3ca9274cf47fcb15e43a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Thu, 27 Jun 2024 10:43:31 +0200 Subject: [PATCH 27/41] feat: missing return type in replaceUniqueNumericQtiIdentifier --- model/qti/ImportService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/qti/ImportService.php b/model/qti/ImportService.php index f58cbb2d1f..310ee5ab64 100755 --- a/model/qti/ImportService.php +++ b/model/qti/ImportService.php @@ -960,7 +960,7 @@ private function getUniqueNumericQtiIdentifierReplacer(): UniqueNumericQtiIdenti return $this->getServiceManager()->getContainer()->get(UniqueNumericQtiIdentifierReplacer::class); } - private function replaceUniqueNumericQtiIdentifier(string $qtiXml) + private function replaceUniqueNumericQtiIdentifier(string $qtiXml): string { return $this->getUniqueNumericQtiIdentifierReplacer()->replace($qtiXml); } From c679b6b032de24be1a10cae7cab6e68de7af0158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Thu, 27 Jun 2024 10:45:17 +0200 Subject: [PATCH 28/41] feat: provide comment for getNumericIdentifier --- model/qti/parser/UniqueNumericQtiIdentifierReplacer.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php b/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php index a92873b780..da5a39d661 100644 --- a/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php +++ b/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php @@ -54,6 +54,10 @@ public function replace(string $qti): string return $doc->saveXML(); } + /** + * This will return 9 digits unique numeric identifier base on time and random number + * i.e: 123456789 + */ private function getNumericIdentifier(): string { return substr((string)floor(time() / 1000), 0, 7) From d914f17eeee9867f8aaa1318ddae3b4c08e1410d Mon Sep 17 00:00:00 2001 From: Karol Stelmaczonek Date: Thu, 27 Jun 2024 13:11:14 +0200 Subject: [PATCH 29/41] feature: move item identifier generation to helper and add unit tests --- views/js/qtiCreator/helper/itemIdentifier.js | 37 +++++++++++++++++++ views/js/qtiCreator/helper/itemLoader.js | 21 +++-------- .../helper/itemIdentifier/test.html | 33 +++++++++++++++++ .../qtiCreator/helper/itemIdentifier/test.js | 18 +++++++++ 4 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 views/js/qtiCreator/helper/itemIdentifier.js create mode 100644 views/js/test/qtiCreator/helper/itemIdentifier/test.html create mode 100644 views/js/test/qtiCreator/helper/itemIdentifier/test.js diff --git a/views/js/qtiCreator/helper/itemIdentifier.js b/views/js/qtiCreator/helper/itemIdentifier.js new file mode 100644 index 0000000000..864e60c80b --- /dev/null +++ b/views/js/qtiCreator/helper/itemIdentifier.js @@ -0,0 +1,37 @@ +/** + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; under version 2 + * of the License (non-upgradable). + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * 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) 2024 (original work) Open Assessment Technologies SA ; + */ + +define([], function() { + const itemIdentifier = { + uniqueNumericIdentifier: function uniqueNumericIdentifier() { + const dateString = Math.floor(Date.now() / 100000).toString().slice(0,7); + const randString = (Math.floor(Math.random() * 90) + 10).toString().slice(0, 2); + return dateString + randString; + }, + defaultIdentifier: function defaultIdentifier(uri, qtiIdentifier) { + if (!uri || !qtiIdentifier) { + throw new Error('Missing uri or qtiIdentifier'); + } + const pos = uri.lastIndexOf('#'); + // identifier by default should be no more then 32 + return uri.substring(pos + 1, pos + 1 + qtiIdentifier.maxQtiIdLength); + } + } + + return itemIdentifier; +}); diff --git a/views/js/qtiCreator/helper/itemLoader.js b/views/js/qtiCreator/helper/itemLoader.js index 2c2f7da724..cdf6bf4eef 100644 --- a/views/js/qtiCreator/helper/itemLoader.js +++ b/views/js/qtiCreator/helper/itemLoader.js @@ -24,21 +24,10 @@ define([ 'taoQtiItem/qtiItem/helper/itemScore', 'core/dataProvider/request', 'taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier', - 'taoQtiItem/qtiCreator/helper/languages' -], function ($, _, Loader, Item, qtiClasses, itemScoreHelper, request, qtiIdentifier, languages) { + 'taoQtiItem/qtiCreator/helper/languages', + 'taoQtiItem/qtiCreator/helper/itemIdentifier' +], function ($, _, Loader, Item, qtiClasses, itemScoreHelper, request, qtiIdentifier, languages, itemIdentifier) { 'use strict'; - const _generateIdentifier = function _generateIdentifier(uri) { - const pos = uri.lastIndexOf('#'); - // identifier by default should be no more then 32 - return uri.substring(pos + 1, pos + 1 + qtiIdentifier.maxQtiIdLength); - }; - - const _generateUniqueNumericIdentifier = function _generateUniqueNumericIdentifier() { - const dateString = Math.floor(Date.now() / 100000).toString().slice(0,7); - const randString = (Math.floor(Math.random() * 90) + 10).toString().slice(0, 2); - return dateString + randString; - } - const decodeHtml = function (str) { const map = { '&': '&', @@ -124,9 +113,9 @@ define([ } else { let identifier; if (config.identifierGenerationStrategy === 'uniqueNumeric') { - identifier = _generateUniqueNumericIdentifier(); + identifier = itemIdentifier.uniqueNumericIdentifier(); } else { - identifier = _generateIdentifier(config.uri); + identifier = itemIdentifier.defaultIdentifier(config.uri, qtiIdentifier); } const newItem = new Item().id(identifier).attr('title', config.label); diff --git a/views/js/test/qtiCreator/helper/itemIdentifier/test.html b/views/js/test/qtiCreator/helper/itemIdentifier/test.html new file mode 100644 index 0000000000..1ee4c877ab --- /dev/null +++ b/views/js/test/qtiCreator/helper/itemIdentifier/test.html @@ -0,0 +1,33 @@ + + + + + Test - itemIdentifier + + + + + + + + + + + +
+
+ + diff --git a/views/js/test/qtiCreator/helper/itemIdentifier/test.js b/views/js/test/qtiCreator/helper/itemIdentifier/test.js new file mode 100644 index 0000000000..f4ab269a29 --- /dev/null +++ b/views/js/test/qtiCreator/helper/itemIdentifier/test.js @@ -0,0 +1,18 @@ +define(['taoQtiItem/qtiCreator/helper/itemIdentifier'], function ( + itemIdentifier, +) { + QUnit.module('itemIdentifier', function (hooks) { + QUnit.test('generate uniqueNumericIdentifier', function (assert) { + const identifier = itemIdentifier.uniqueNumericIdentifier(); + + assert.ok( + identifier.length === 9, + 'Identifier has the correct length (9 characters)', + ); + assert.ok( + /^\d{9}$/.test(identifier), + 'Identifier is numeric and has 9 digits', + ); + }); + }); +}); From e88ad060d2ad99852d1d706f25eb6f989d2e18ff Mon Sep 17 00:00:00 2001 From: Karol Stelmaczonek Date: Thu, 27 Jun 2024 14:15:20 +0200 Subject: [PATCH 30/41] feature: change to shorthand --- views/js/qtiCreator/helper/itemIdentifier.js | 30 +++++++++++--------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/views/js/qtiCreator/helper/itemIdentifier.js b/views/js/qtiCreator/helper/itemIdentifier.js index 864e60c80b..e36cac6104 100644 --- a/views/js/qtiCreator/helper/itemIdentifier.js +++ b/views/js/qtiCreator/helper/itemIdentifier.js @@ -18,20 +18,24 @@ define([], function() { const itemIdentifier = { - uniqueNumericIdentifier: function uniqueNumericIdentifier() { - const dateString = Math.floor(Date.now() / 100000).toString().slice(0,7); - const randString = (Math.floor(Math.random() * 90) + 10).toString().slice(0, 2); - return dateString + randString; - }, - defaultIdentifier: function defaultIdentifier(uri, qtiIdentifier) { - if (!uri || !qtiIdentifier) { - throw new Error('Missing uri or qtiIdentifier'); - } - const pos = uri.lastIndexOf('#'); - // identifier by default should be no more then 32 - return uri.substring(pos + 1, pos + 1 + qtiIdentifier.maxQtiIdLength); + uniqueNumericIdentifier() { + const dateString = Math.floor(Date.now() / 100000) + .toString() + .slice(0, 7); + const randString = (Math.floor(Math.random() * 90) + 10) + .toString() + .slice(0, 2); + return dateString + randString; + }, + defaultIdentifier(uri, qtiIdentifier) { + if (!uri || !qtiIdentifier) { + throw new Error("Missing uri or qtiIdentifier"); } - } + const pos = uri.lastIndexOf("#"); + // identifier by default should be no more then 32 + return uri.substring(pos + 1, pos + 1 + qtiIdentifier.maxQtiIdLength); + }, + }; return itemIdentifier; }); From 85c650fbd80c8c065c8ad4f8fe474662af767184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Thu, 27 Jun 2024 16:36:35 +0200 Subject: [PATCH 31/41] feat: override item identifier validation when ff is enabled --- ...UniqueNumericQtiIdentifierClientConfig.php | 3 +- ...ItemIdentifierValidatorServiceProvider.php | 5 +- .../qti/validator/ItemIdentifierValidator.php | 15 +++++- .../validator/ItemIdentifierValidatorTest.php | 47 +++++++++++++++---- 4 files changed, 56 insertions(+), 14 deletions(-) diff --git a/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php b/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php index e2228775d1..c5d4f7c0a7 100644 --- a/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php +++ b/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php @@ -27,6 +27,7 @@ class UniqueNumericQtiIdentifierClientConfig implements FeatureFlagConfigHandlerInterface { + public const QTI_ID_PATTERN = '/^\\d{9}$/'; public function __construct(FeatureFlagCheckerInterface $featureFlagChecker) { $this->featureFlagChecker = $featureFlagChecker; @@ -34,7 +35,7 @@ public function __construct(FeatureFlagCheckerInterface $featureFlagChecker) public function __invoke(array $configs): array { if ($this->isEnabled()) { - $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['qtiIdPattern'] = '/^\\d{9}$/'; + $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['qtiIdPattern'] = self::QTI_ID_PATTERN; $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['invalidQtiIdMessage'] = 'The QTI identifier must be a 9-digit number.'; $configs['taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier']['isDisabled'] = true; diff --git a/model/qti/ServiceProvider/ItemIdentifierValidatorServiceProvider.php b/model/qti/ServiceProvider/ItemIdentifierValidatorServiceProvider.php index bf0d9389fd..c85ea65331 100644 --- a/model/qti/ServiceProvider/ItemIdentifierValidatorServiceProvider.php +++ b/model/qti/ServiceProvider/ItemIdentifierValidatorServiceProvider.php @@ -23,10 +23,12 @@ namespace oat\taoQtiItem\model\qti\ServiceProvider; use oat\generis\model\DependencyInjection\ContainerServiceProviderInterface; +use oat\tao\model\featureFlag\FeatureFlagChecker; use oat\taoQtiItem\model\qti\validator\ItemIdentifierValidator; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use function Symfony\Component\DependencyInjection\Loader\Configurator\env; +use function Symfony\Component\DependencyInjection\Loader\Configurator\service; class ItemIdentifierValidatorServiceProvider implements ContainerServiceProviderInterface { @@ -55,7 +57,8 @@ public function __invoke(ContainerConfigurator $configurator): void ItemIdentifierValidator::DEFAULT_PATTERN_PARAMETER_NAME, ItemIdentifierValidator::ENV_QTI_IDENTIFIER_VALIDATOR_PATTERN ) - ) + ), + service(FeatureFlagChecker::class), ] ); } diff --git a/model/qti/validator/ItemIdentifierValidator.php b/model/qti/validator/ItemIdentifierValidator.php index 2b280babf2..d04a98929d 100644 --- a/model/qti/validator/ItemIdentifierValidator.php +++ b/model/qti/validator/ItemIdentifierValidator.php @@ -24,6 +24,8 @@ namespace oat\taoQtiItem\model\qti\validator; use common_exception_Error; +use oat\tao\model\featureFlag\FeatureFlagChecker; +use oat\taoQtiItem\model\FeatureFlag\UniqueNumericQtiIdentifierClientConfig; use oat\taoQtiItem\model\qti\Item; class ItemIdentifierValidator @@ -35,10 +37,12 @@ class ItemIdentifierValidator /** @var string */ private $pattern; + private FeatureFlagChecker $featureFlagChecker; - public function __construct(string $pattern = self::DEFAULT_PATTERN) + public function __construct(string $pattern = self::DEFAULT_PATTERN, FeatureFlagChecker $featureFlagChecker) { $this->pattern = $pattern; + $this->featureFlagChecker = $featureFlagChecker; } /** @@ -46,9 +50,16 @@ public function __construct(string $pattern = self::DEFAULT_PATTERN) */ public function validate(Item $item): void { - //validate assessmentItem identifier + $this->overrideEnvironmentVariablePattern(); if (preg_match($this->pattern, $item->getAttributeValue('identifier')) !== 1) { throw new common_exception_Error("The item identifier is not valid"); } } + + private function overrideEnvironmentVariablePattern(): void + { + if ($this->featureFlagChecker->isEnabled('FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER')) { + $this->pattern = UniqueNumericQtiIdentifierClientConfig::QTI_ID_PATTERN; + } + } } diff --git a/test/unit/model/qti/validator/ItemIdentifierValidatorTest.php b/test/unit/model/qti/validator/ItemIdentifierValidatorTest.php index 7d81cf158b..452881162d 100644 --- a/test/unit/model/qti/validator/ItemIdentifierValidatorTest.php +++ b/test/unit/model/qti/validator/ItemIdentifierValidatorTest.php @@ -24,6 +24,7 @@ use common_exception_Error; use oat\generis\test\TestCase; +use oat\tao\model\featureFlag\FeatureFlagChecker; use oat\taoQtiItem\model\qti\Item; use oat\taoQtiItem\model\qti\validator\ItemIdentifierValidator; @@ -35,53 +36,79 @@ class ItemIdentifierValidatorTest extends TestCase protected function setUp(): void { $this->item = $this->createMock(Item::class); + $this->featureFlagChecker = $this->createMock(FeatureFlagChecker::class); + $this->subject = new ItemIdentifierValidator( + '/^[a-zA-Z_]{1}[a-zA-Z0-9_\.-]*$/u', + $this->featureFlagChecker + ); } public function testValidationSuccess(): void { - $subject = new ItemIdentifierValidator(); + $this->featureFlagChecker->expects($this->once()) + ->method('isEnabled') + ->willReturn(false); $this->item->expects($this->once()) ->method('getAttributeValue') ->willReturn('some-fake-id-64228-217055'); - $subject->validate($this->item); + $this->subject->validate($this->item); } public function testValidationSuccessWithDifferentPattern(): void { - $subject = new ItemIdentifierValidator('/^[a-zA-Z_]{1}[a-zA-Z0-9_-]*$/u'); + $this->featureFlagChecker->expects($this->once()) + ->method('isEnabled') + ->willReturn(false); $this->item->expects($this->once()) ->method('getAttributeValue') ->willReturn('some-fake-id-64228-217055-not-allowed-dots'); - $subject->validate($this->item); + $this->subject->validate($this->item); } public function testValidationFailureThrowsException(): void { - $this->expectException(common_exception_Error::class); + $this->featureFlagChecker->expects($this->once()) + ->method('isEnabled') + ->willReturn(false); - $subject = new ItemIdentifierValidator(); + $this->expectException(common_exception_Error::class); $this->item->expects($this->once()) ->method('getAttributeValue') ->willReturn('$some.fake.id.64228.217055'); - $subject->validate($this->item); + $this->subject->validate($this->item); } public function testValidationFailureWithDifferentPatternThrowsException(): void { + $this->featureFlagChecker->expects($this->once()) + ->method('isEnabled') + ->willReturn(false); + $this->expectException(common_exception_Error::class); - $subject = new ItemIdentifierValidator('/^[a-zA-Z_]{1}[a-zA-Z0-9_-]*$/u'); + $this->item->expects($this->once()) + ->method('getAttributeValue') + ->willReturn('some.fake.id.64228.217055!not.allowed.dots'); + + $this->subject->validate($this->item); + } + + public function testValidationSuccessWithFeatureFlag() + { + $this->featureFlagChecker->expects($this->once()) + ->method('isEnabled') + ->willReturn(true); $this->item->expects($this->once()) ->method('getAttributeValue') - ->willReturn('some.fake.id.64228.217055.not.allowed.dots'); + ->willReturn('999999999'); - $subject->validate($this->item); + $this->subject->validate($this->item); } } From 9282528531f4d2964c40fbf50e4b2a19e38e8f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Fri, 28 Jun 2024 08:13:46 +0200 Subject: [PATCH 32/41] feat: replace ff const with string --- model/qti/parser/UniqueNumericQtiIdentifierReplacer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php b/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php index da5a39d661..b7677d3a18 100644 --- a/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php +++ b/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php @@ -38,7 +38,7 @@ public function __construct(FeatureFlagChecker $featureFlagChecker, QtiXmlLoader } public function replace(string $qti): string { - if (!$this->featureFlagChecker->isEnabled(FeatureFlagChecker::FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER)) { + if (!$this->featureFlagChecker->isEnabled('FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER')) { return $qti; } $doc = $this->qtiXmlLoader->load($qti); From 923f59468da027be90cbbb89149e5b58350beed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Tue, 2 Jul 2024 12:42:06 +0200 Subject: [PATCH 33/41] feat: qti pattern according to normalised string. --- model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php b/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php index c5d4f7c0a7..bfa5e58dc5 100644 --- a/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php +++ b/model/FeatureFlag/UniqueNumericQtiIdentifierClientConfig.php @@ -27,7 +27,7 @@ class UniqueNumericQtiIdentifierClientConfig implements FeatureFlagConfigHandlerInterface { - public const QTI_ID_PATTERN = '/^\\d{9}$/'; + public const QTI_ID_PATTERN = '/^[^\t\n\r]*$/'; public function __construct(FeatureFlagCheckerInterface $featureFlagChecker) { $this->featureFlagChecker = $featureFlagChecker; From 2bcb190592418e8cab98c47e831bf5f0e65992a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Tue, 2 Jul 2024 15:54:41 +0200 Subject: [PATCH 34/41] feat: ItemIdentifierValidator __construct param order changed --- .../ItemIdentifierValidatorServiceProvider.php | 4 ++-- model/qti/validator/ItemIdentifierValidator.php | 2 +- test/unit/model/qti/validator/ItemIdentifierValidatorTest.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/model/qti/ServiceProvider/ItemIdentifierValidatorServiceProvider.php b/model/qti/ServiceProvider/ItemIdentifierValidatorServiceProvider.php index c85ea65331..8212b4496a 100644 --- a/model/qti/ServiceProvider/ItemIdentifierValidatorServiceProvider.php +++ b/model/qti/ServiceProvider/ItemIdentifierValidatorServiceProvider.php @@ -51,14 +51,14 @@ public function __invoke(ContainerConfigurator $configurator): void ->public() ->args( [ + service(FeatureFlagChecker::class), env( sprintf( 'default:%s:%s', ItemIdentifierValidator::DEFAULT_PATTERN_PARAMETER_NAME, ItemIdentifierValidator::ENV_QTI_IDENTIFIER_VALIDATOR_PATTERN ) - ), - service(FeatureFlagChecker::class), + ) ] ); } diff --git a/model/qti/validator/ItemIdentifierValidator.php b/model/qti/validator/ItemIdentifierValidator.php index d04a98929d..90d51492c3 100644 --- a/model/qti/validator/ItemIdentifierValidator.php +++ b/model/qti/validator/ItemIdentifierValidator.php @@ -39,7 +39,7 @@ class ItemIdentifierValidator private $pattern; private FeatureFlagChecker $featureFlagChecker; - public function __construct(string $pattern = self::DEFAULT_PATTERN, FeatureFlagChecker $featureFlagChecker) + public function __construct(FeatureFlagChecker $featureFlagChecker, string $pattern = self::DEFAULT_PATTERN) { $this->pattern = $pattern; $this->featureFlagChecker = $featureFlagChecker; diff --git a/test/unit/model/qti/validator/ItemIdentifierValidatorTest.php b/test/unit/model/qti/validator/ItemIdentifierValidatorTest.php index 452881162d..73e8515e9d 100644 --- a/test/unit/model/qti/validator/ItemIdentifierValidatorTest.php +++ b/test/unit/model/qti/validator/ItemIdentifierValidatorTest.php @@ -38,8 +38,8 @@ protected function setUp(): void $this->item = $this->createMock(Item::class); $this->featureFlagChecker = $this->createMock(FeatureFlagChecker::class); $this->subject = new ItemIdentifierValidator( + $this->featureFlagChecker, '/^[a-zA-Z_]{1}[a-zA-Z0-9_\.-]*$/u', - $this->featureFlagChecker ); } From 90b1962acb9b0914b5c1fbfebdbdb3130338e519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Tue, 9 Jul 2024 16:20:00 +0200 Subject: [PATCH 35/41] feat: fix validators --- .../widgets/helpers/qtiResponseIdentifier.js | 35 +++++++++++++++++++ .../qtiCreator/widgets/helpers/validators.js | 10 +++--- 2 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 views/js/qtiCreator/widgets/helpers/qtiResponseIdentifier.js diff --git a/views/js/qtiCreator/widgets/helpers/qtiResponseIdentifier.js b/views/js/qtiCreator/widgets/helpers/qtiResponseIdentifier.js new file mode 100644 index 0000000000..863cb5792c --- /dev/null +++ b/views/js/qtiCreator/widgets/helpers/qtiResponseIdentifier.js @@ -0,0 +1,35 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; under version 2 + * of the License (non-upgradable). + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * 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) 2022 (original work) Open Assessment Technologies SA + * + */ +define(['module', 'i18n'], function (module, __) { + 'use strict'; + + const qtiIdPattern = module.config().qtiResponseIdPattern || '/^[a-zA-Z_]{1}[a-zA-Z0-9_.-]*$/u'; + const [, patternContent, flags] = qtiIdPattern.match(/^\/(.+)\/(\w*)$/); + const defaultInvalidQtiIdMessage = 'Identifiers must start with a letter or an underscore and contain only letters, numbers, dots, underscores ( _ ), or hyphens ( - ).'; + const message = module.config().invalidQtiIdMessage || defaultInvalidQtiIdMessage; + const invalidQtiIdMessage = __(message); + const isDisabled = module.config().isDisabled || false; + + return { + pattern: new RegExp(patternContent, flags), + invalidQtiIdMessage, + maxQtiIdLength: 32, + isDisabled + }; +}); diff --git a/views/js/qtiCreator/widgets/helpers/validators.js b/views/js/qtiCreator/widgets/helpers/validators.js index 76f34ab48f..1aa5114046 100644 --- a/views/js/qtiCreator/widgets/helpers/validators.js +++ b/views/js/qtiCreator/widgets/helpers/validators.js @@ -22,17 +22,17 @@ define([ 'i18n', 'taoQtiItem/qtiItem/core/Element', 'taoQtiItem/qtiCreator/model/helper/invalidator', - 'taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier' -], function (validators, _, __, Element, invalidator, qtiIdentifier) { + 'taoQtiItem/qtiCreator/widgets/helpers/qtiResponseIdentifier' +], function (validators, _, __, Element, invalidator, qtiResponseIdentifier) { 'use strict'; - const _qtiIdPattern = qtiIdentifier.pattern; + const _qtiIdPattern = qtiResponseIdentifier.pattern; const typeToMessage = { item: __('Invalid identifier'), response: __('Invalid response identifier'), outcome: __('Invalid Outcome Declaration') }; - const invalidIdentifier = qtiIdentifier.invalidQtiIdMessage; + const invalidIdentifier = qtiResponseIdentifier.invalidQtiIdMessage; const validateIdentifier = (value, callback, options, type) => { if (typeof callback === 'function') { const valid = _qtiIdPattern.test(value); @@ -50,7 +50,7 @@ define([ const qtiValidators = [ { - name: 'qtiIdentifier', + name: 'qtiResponseIdentifier', message: `${typeToMessage.item}
${invalidIdentifier}`, validate: function validate(value, callback, options) { validateIdentifier(value, callback, options, 'item'); From 43b9b6eea50802d47cd37af8ee023f68ec24f3f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 10 Jul 2024 10:47:37 +0200 Subject: [PATCH 36/41] feat: Duplicate Item will use numeric identifier when feature flag enabled --- ...ifierGenerationStrategyServiceProvider.php | 8 ++++- model/qti/copyist/QtiXmlDataManager.php | 25 +++++++++++-- .../IdentifierGenerator.php | 28 +++++++++++++++ .../UniqueNumericQtiIdentifierGenerator.php | 36 +++++++++++++++++++ .../UniqueNumericQtiIdentifierReplacer.php | 17 +++------ ...niqueNumericQtiIdentifierGeneratorTest.php | 36 +++++++++++++++++++ ...UniqueNumericQtiIdentifierReplacerTest.php | 13 +++++-- 7 files changed, 145 insertions(+), 18 deletions(-) create mode 100644 model/qti/identifierGenerator/IdentifierGenerator.php create mode 100644 model/qti/identifierGenerator/UniqueNumericQtiIdentifierGenerator.php create mode 100644 test/unit/model/qti/identifierGenerator/UniqueNumericQtiIdentifierGeneratorTest.php diff --git a/model/qti/ServiceProvider/IdentifierGenerationStrategyServiceProvider.php b/model/qti/ServiceProvider/IdentifierGenerationStrategyServiceProvider.php index 638ac30e09..dc76cea45a 100644 --- a/model/qti/ServiceProvider/IdentifierGenerationStrategyServiceProvider.php +++ b/model/qti/ServiceProvider/IdentifierGenerationStrategyServiceProvider.php @@ -26,6 +26,8 @@ use oat\tao\model\featureFlag\FeatureFlagChecker; use common_ext_ExtensionsManager as ExtensionsManager; use oat\taoQtiItem\helpers\QtiXmlLoader; +use oat\taoQtiItem\model\qti\identifierGenerator\IdentifierGenerator; +use oat\taoQtiItem\model\qti\identifierGenerator\UniqueNumericQtiIdentifierGenerator; use oat\taoQtiItem\model\qti\parser\UniqueNumericQtiIdentifierReplacer; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; @@ -42,10 +44,14 @@ public function __invoke(ContainerConfigurator $configurator): void service(ExtensionsManager::SERVICE_ID) ]); + $services->set(IdentifierGenerator::class, UniqueNumericQtiIdentifierGenerator::class) + ->public(); + $services->set(UniqueNumericQtiIdentifierReplacer::class, UniqueNumericQtiIdentifierReplacer::class) ->args([ service(FeatureFlagChecker::class), - service(QtiXmlLoader::class) + service(QtiXmlLoader::class), + service(IdentifierGenerator::class) ]) ->public(); } diff --git a/model/qti/copyist/QtiXmlDataManager.php b/model/qti/copyist/QtiXmlDataManager.php index 042f59064f..cead13cfa2 100644 --- a/model/qti/copyist/QtiXmlDataManager.php +++ b/model/qti/copyist/QtiXmlDataManager.php @@ -34,8 +34,10 @@ use oat\oatbox\filesystem\Directory; use oat\oatbox\log\LoggerAwareTrait; use oat\oatbox\service\ConfigurableService; +use oat\tao\model\featureFlag\FeatureFlagChecker; use oat\taoQtiItem\helpers\QtiFile; use oat\oatbox\filesystem\File; +use oat\taoQtiItem\model\qti\identifierGenerator\IdentifierGenerator; use tao_models_classes_FileNotFoundException; use taoItems_models_classes_ItemsService; @@ -108,15 +110,13 @@ public function replaceItemIdentifier( private function replaceFileContent(File $file, string $fromSourceId, string $toSourceId): void { if (preg_match('/' . QtiFile::FILE . '$/', $file->getBasename()) === 1) { - $replaceWith = $this->detectId($toSourceId); - $xml = $file->read(); $dom = new DOMDocument('1.0', 'UTF-8'); if ($dom->loadXML($xml) === true) { $assessmentItemNodes = $dom->getElementsByTagName('assessmentItem'); /** @var DOMElement $item */ foreach ($assessmentItemNodes as $item) { - $item->setAttribute('identifier', $replaceWith); + $item->setAttribute('identifier', $this->getQtiIdentifier($toSourceId)); } } else { $this->logWarning('Qti.xml does not have a valid xml, identifier will not be replaced'); @@ -162,6 +162,15 @@ private function getAppNamespacePrefix(): string return $this->prefix; } + private function getQtiIdentifier(string $toSourceId): string + { + if ($this->getFeatureFlagChecker()->isEnabled('FEATURE_FLAG_UNIQUE_NUMERIC_QTI_IDENTIFIER')) { + return $this->getIdentifierGenerator()->generate(); + } + + return $this->detectId($toSourceId); + } + /** * Get serializer to persist filesystem object * @@ -171,4 +180,14 @@ protected function getFileReferenceSerializer(): FileReferenceSerializer { return $this->getServiceLocator()->get(FileReferenceSerializer::SERVICE_ID); } + + private function getFeatureFlagChecker(): FeatureFlagChecker + { + return $this->getServiceManager()->getContainer()->get(FeatureFlagChecker::class); + } + + private function getIdentifierGenerator() + { + return $this->getServiceManager()->getContainer()->get(IdentifierGenerator::class); + } } diff --git a/model/qti/identifierGenerator/IdentifierGenerator.php b/model/qti/identifierGenerator/IdentifierGenerator.php new file mode 100644 index 0000000000..ca022294a6 --- /dev/null +++ b/model/qti/identifierGenerator/IdentifierGenerator.php @@ -0,0 +1,28 @@ +featureFlagChecker = $featureFlagChecker; $this->qtiXmlLoader = $qtiXmlLoader; + $this->identifierGenerator = $identifierGenerator; } public function replace(string $qti): string { @@ -48,19 +51,9 @@ public function replace(string $qti): string $identifierNodes = $xpath->query('//qti:assessmentItem/@identifier'); foreach ($identifierNodes as $identifierNode) { - $identifierNode->nodeValue = $this->getNumericIdentifier(); + $identifierNode->nodeValue = $this->identifierGenerator->generate(); } return $doc->saveXML(); } - - /** - * This will return 9 digits unique numeric identifier base on time and random number - * i.e: 123456789 - */ - private function getNumericIdentifier(): string - { - return substr((string)floor(time() / 1000), 0, 7) - . substr((string)floor(mt_rand(10, 100)), 0, 2); - } } diff --git a/test/unit/model/qti/identifierGenerator/UniqueNumericQtiIdentifierGeneratorTest.php b/test/unit/model/qti/identifierGenerator/UniqueNumericQtiIdentifierGeneratorTest.php new file mode 100644 index 0000000000..019be23dac --- /dev/null +++ b/test/unit/model/qti/identifierGenerator/UniqueNumericQtiIdentifierGeneratorTest.php @@ -0,0 +1,36 @@ +generate(); + $this->assertRegExp('/^\d{9}$/', $identifier); + } +} diff --git a/test/unit/model/qti/parser/UniqueNumericQtiIdentifierReplacerTest.php b/test/unit/model/qti/parser/UniqueNumericQtiIdentifierReplacerTest.php index c9f55ed9a7..fbaf432d83 100644 --- a/test/unit/model/qti/parser/UniqueNumericQtiIdentifierReplacerTest.php +++ b/test/unit/model/qti/parser/UniqueNumericQtiIdentifierReplacerTest.php @@ -22,11 +22,11 @@ namespace oat\taoQtiItem\test\unit\model\qti\parser; -use DOMAttr; use DOMDocument; use DOMXPath; use oat\tao\model\featureFlag\FeatureFlagChecker; use oat\taoQtiItem\helpers\QtiXmlLoader; +use oat\taoQtiItem\model\qti\identifierGenerator\IdentifierGenerator; use oat\taoQtiItem\model\qti\parser\UniqueNumericQtiIdentifierReplacer; use PHPUnit\Framework\TestCase; @@ -36,15 +36,21 @@ public function setUp(): void { $this->featureFlagCheckerMock = $this->createMock(FeatureFlagChecker::class); $this->qtiXmlLoaderMock = $this->createMock(QtiXmlLoader::class); + $this->identifierGeneratorMock = $this->createMock(IdentifierGenerator::class); $this->subject = new UniqueNumericQtiIdentifierReplacer( $this->featureFlagCheckerMock, - $this->qtiXmlLoaderMock + $this->qtiXmlLoaderMock, + $this->identifierGeneratorMock ); } public function testReplace(): void { + $this->identifierGeneratorMock + ->expects(self::once()) + ->method('generate') + ->willReturn('123456789'); $qti = file_get_contents(__DIR__ . '/qti.xml'); $this->featureFlagCheckerMock->method('isEnabled') @@ -72,6 +78,9 @@ public function testReplace(): void public function testReplaceWhenFeatureFlagDisabled(): void { + $this->identifierGeneratorMock + ->expects($this->never()) + ->method('generate'); $qti = file_get_contents(__DIR__ . '/qti.xml'); $this->featureFlagCheckerMock->method('isEnabled') From 31978d1ab706a235fcdac3513ab7a8f3e2440803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Wed, 10 Jul 2024 15:42:58 +0200 Subject: [PATCH 37/41] fix: llicnece update --- views/js/qtiCreator/widgets/helpers/qtiResponseIdentifier.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/js/qtiCreator/widgets/helpers/qtiResponseIdentifier.js b/views/js/qtiCreator/widgets/helpers/qtiResponseIdentifier.js index 863cb5792c..ed337ece85 100644 --- a/views/js/qtiCreator/widgets/helpers/qtiResponseIdentifier.js +++ b/views/js/qtiCreator/widgets/helpers/qtiResponseIdentifier.js @@ -13,7 +13,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) 2022 (original work) Open Assessment Technologies SA + * Copyright (c) 2024 (original work) Open Assessment Technologies SA * */ define(['module', 'i18n'], function (module, __) { From f4c79b4b916994c1294176f9388bd51a2e98b253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Fri, 12 Jul 2024 13:09:32 +0200 Subject: [PATCH 38/41] feat: getIdentifierGenerator return type --- model/qti/copyist/QtiXmlDataManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/qti/copyist/QtiXmlDataManager.php b/model/qti/copyist/QtiXmlDataManager.php index cead13cfa2..1338a4c07f 100644 --- a/model/qti/copyist/QtiXmlDataManager.php +++ b/model/qti/copyist/QtiXmlDataManager.php @@ -186,7 +186,7 @@ private function getFeatureFlagChecker(): FeatureFlagChecker return $this->getServiceManager()->getContainer()->get(FeatureFlagChecker::class); } - private function getIdentifierGenerator() + private function getIdentifierGenerator(): IdentifierGenerator { return $this->getServiceManager()->getContainer()->get(IdentifierGenerator::class); } From a111fe7cac938a8c03d0b523250e7c046151a660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Fri, 12 Jul 2024 13:25:35 +0200 Subject: [PATCH 39/41] feat: QtiXmlDataManagerTest fixed --- test/unit/model/qti/copyist/QtiXmlDataManagerTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/unit/model/qti/copyist/QtiXmlDataManagerTest.php b/test/unit/model/qti/copyist/QtiXmlDataManagerTest.php index 8f058a3d22..7e592e4dd8 100644 --- a/test/unit/model/qti/copyist/QtiXmlDataManagerTest.php +++ b/test/unit/model/qti/copyist/QtiXmlDataManagerTest.php @@ -32,6 +32,7 @@ use oat\generis\model\fileReference\FileReferenceSerializer; use oat\generis\test\MockObject; use oat\generis\test\TestCase; +use oat\tao\model\featureFlag\FeatureFlagChecker; use oat\taoQtiItem\model\qti\copyist\QtiXmlDataManager; use tao_models_classes_FileNotFoundException; use taoItems_models_classes_ItemsService; @@ -96,9 +97,12 @@ protected function setUp(): void ->method('getResource') ->willReturnOnConsecutiveCalls($itemResourceMock); + $this->featureFlagCheckerMock = $this->createMock(FeatureFlagChecker::class); + $serviceLocatorMock = $this->getServiceLocatorMock([ FileReferenceSerializer::SERVICE_ID => $fileReferenceSerializerMock, taoItems_models_classes_ItemsService::class => $itemsServiceMock, + FeatureFlagChecker::class => $this->featureFlagCheckerMock, ]); $self = $this; From a767606a9b3c058607672d2ee0d78312f0f5a2a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Marsza=C5=82?= Date: Fri, 12 Jul 2024 13:30:22 +0200 Subject: [PATCH 40/41] feat: phpcs UniqueNumericQtiIdentifierReplacer --- model/qti/parser/UniqueNumericQtiIdentifierReplacer.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php b/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php index da7c31db74..f7ab76c539 100644 --- a/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php +++ b/model/qti/parser/UniqueNumericQtiIdentifierReplacer.php @@ -33,8 +33,11 @@ class UniqueNumericQtiIdentifierReplacer private QtiXmlLoader $qtiXmlLoader; private IdentifierGenerator $identifierGenerator; - public function __construct(FeatureFlagChecker $featureFlagChecker, QtiXmlLoader $qtiXmlLoader, IdentifierGenerator $identifierGenerator) - { + public function __construct( + FeatureFlagChecker $featureFlagChecker, + QtiXmlLoader $qtiXmlLoader, + IdentifierGenerator $identifierGenerator + ) { $this->featureFlagChecker = $featureFlagChecker; $this->qtiXmlLoader = $qtiXmlLoader; $this->identifierGenerator = $identifierGenerator; From d3463aece883b006e30f69103a3b976c6fc6876f Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 12 Jul 2024 13:42:38 +0200 Subject: [PATCH 41/41] chore: bundle assets --- views/js/loader/taoQtiItem.min.js | 2 +- views/js/loader/taoQtiItem.min.js.map | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/views/js/loader/taoQtiItem.min.js b/views/js/loader/taoQtiItem.min.js index 6697966201..0fbd26a45a 100644 --- a/views/js/loader/taoQtiItem.min.js +++ b/views/js/loader/taoQtiItem.min.js @@ -1,2 +1,2 @@ -define("taoQtiItem/portableElementRegistry/factory/factory",["lodash","core/promise","core/eventifier"],function(_,Promise,eventifier){"use strict";var _requirejs=window.require,_defaultLoadingOptions={reload:!1},loadModuleConfig=function loadModuleConfig(manifest){return new Promise(function(resolve,reject){var requireConfigAliases={},reqConfigs=[],modules={},baseUrl;return manifest&&manifest.runtime?void(baseUrl=manifest.baseUrl,_.isArray(manifest.runtime.config)&&manifest.runtime.config.length&&_.forEach(manifest.runtime.config,function(pciConfig){_.isString(pciConfig)?reqConfigs.push("json!"+baseUrl+"/"+pciConfig):pciConfig.data?modules=_.defaults(modules,pciConfig.data.paths||{}):pciConfig.file&&reqConfigs.push("json!"+baseUrl+"/"+pciConfig.file)}),require(reqConfigs,function(){var runtimeModules={};requireConfigAliases[manifest.typeIdentifier]=baseUrl,"IMSPCI"===manifest.model&&(modules=_.reduce(arguments,function(acc,conf){return _.defaults(acc,conf.paths||{})},modules),_.forEach(manifest.runtime.modules||{},function(paths,id){paths&&(_.isString(paths)||_.isArray(paths)&&paths.length)&&(runtimeModules[id]=paths)}),modules=_.merge(modules,runtimeModules),_.forEach(modules,function(paths,id){paths=_.isArray(paths)?paths:[paths],requireConfigAliases[id]=_.map(paths,function(path){return baseUrl+"/"+path.replace(/\.js$/,"")})})),resolve(requireConfigAliases)},reject)):reject("invalid manifest")})},isPortableElementProvider=function isPortableElementProvider(provider){return provider&&_.isFunction(provider.load)};return function portableElementRegistry(methods){var _loaded=!1,__providers={};return eventifier(_.defaults(methods||{},{_registry:{},get:function get(typeIdentifier,version){if(this._registry[typeIdentifier])return version?_.find(this._registry[typeIdentifier],{version:version}):_.last(this._registry[typeIdentifier])},registerProvider:function registerProvider(moduleName,provider){return __providers[moduleName]=isPortableElementProvider(provider)?provider:null,_loaded=!1,this},resetProviders:function resetProviders(){return __providers={},_loaded=!1,this},loadProviders:function loadProviders(options){var self=this,loadPromise;return loadPromise=_loaded&&!options.reload?Promise.resolve([]):new Promise(function(resolve,reject){var providerLoadingStack=[];_.forIn(__providers,function(provider,id){null===provider&&providerLoadingStack.push(id)}),_requirejs(providerLoadingStack,function(){_.forEach([].slice.call(arguments),function(provider){isPortableElementProvider(provider)&&(__providers[providerLoadingStack.shift()]=provider)}),resolve(__providers),_loaded=!0,self.trigger("providersloaded",__providers)},reject)}),loadPromise},getAllVersions:function getAllVersions(){var all={};return _.forIn(this._registry,function(versions,id){all[id]=_.map(versions,"version")}),all},getRuntime:function getRuntime(typeIdentifier,version){var portableElement=this.get(typeIdentifier,version);return portableElement?_.assign(portableElement.runtime,{id:portableElement.typeIdentifier,label:portableElement.label,baseUrl:portableElement.baseUrl,model:portableElement.model}):void this.trigger("error",{message:"no portable element runtime found",typeIdentifier:typeIdentifier,version:version})},getCreator:function getCreator(typeIdentifier,version){var portableElement=this.get(typeIdentifier,version);return portableElement&&portableElement.creator?_.assign(portableElement.creator,{id:portableElement.typeIdentifier,label:portableElement.label,baseUrl:portableElement.baseUrl,response:portableElement.response,model:portableElement.model,xmlns:portableElement.xmlns}):void this.trigger("error",{message:"no portable element runtime found",typeIdentifier:typeIdentifier,version:version})},getLatestCreators:function getLatestCreators(){var all={};return _.forIn(this._registry,function(versions,id){var lastVersion=_.last(versions);lastVersion.creator&&lastVersion.creator.hook&&lastVersion.enabled&&(all[id]=lastVersion)}),all},getBaseUrl:function getBaseUrl(typeIdentifier,version){var runtime=this.get(typeIdentifier,version);return runtime?runtime.baseUrl:""},loadRuntimes:function loadRuntimes(options){var self=this,loadPromise;return options=_.defaults(options||{},_defaultLoadingOptions),loadPromise=self.loadProviders(options).then(function(providers){var loadStack=[];return _.forEach(providers,function(provider){provider&&loadStack.push(provider.load())}),Promise.all(loadStack).then(function(results){var configLoadingStack=[];return self._registry=_.reduce(results,function(acc,_pcis){return _.merge(acc,_pcis)},self._registry||{}),_.forIn(self._registry,function(versions,typeIdentifier){return!!(_.isArray(options.include)&&0>_.indexOf(options.include,typeIdentifier))||void configLoadingStack.push(loadModuleConfig(self.get(typeIdentifier)))}),Promise.all(configLoadingStack).then(function(moduleConfigs){var requireConfigAliases=_.reduce(moduleConfigs,function(acc,paths){return _.merge(acc,paths)},{});_requirejs.config({paths:requireConfigAliases})})})}),loadPromise.then(function(){self.trigger("runtimesloaded")}).catch(function(err){self.trigger("error",err)}),loadPromise},loadCreators:function loadCreators(options){var self=this,loadPromise;return options=_.defaults(options||{},_defaultLoadingOptions),loadPromise=self.loadRuntimes(options).then(function(){var requiredCreatorHooks=[];return _.forIn(self._registry,function(versions,typeIdentifier){var portableElementModel=self.get(typeIdentifier);if(portableElementModel.creator&&portableElementModel.creator.hook){if(portableElementModel.enabled){if(_.isArray(options.include)&&0>_.indexOf(options.include,typeIdentifier))return!0;}else if(!_.isArray(options.include)||0>_.indexOf(options.include,typeIdentifier))return!0;requiredCreatorHooks.push(portableElementModel.creator.hook.replace(/\.js$/,""))}}),requiredCreatorHooks.length?new Promise(function(resolve,reject){_requirejs(requiredCreatorHooks,function(){var creators={};_.forEach(arguments,function(creatorHook){var id=creatorHook.getTypeIdentifier(),portableElementModel=self.get(id),i=_.findIndex(self._registry[id],{version:portableElementModel.version});0>i?self.trigger("error","no creator found for id/version "+id+"/"+portableElementModel.version):(self._registry[id][i].creator.module=creatorHook,creators[id]=creatorHook)}),resolve(creators)},reject)}):Promise.resolve({})}),loadPromise.then(function(creators){return self.trigger("creatorsloaded",creators),creators}).catch(function(err){self.trigger("error",err)}),loadPromise},enable:function enable(typeIdentifier,version){var portableElement=this.get(typeIdentifier,version);return portableElement&&(portableElement.enabled=!0),this},disable:function disable(typeIdentifier,version){var portableElement=this.get(typeIdentifier,version);return portableElement&&(portableElement.enabled=!1),this},isEnabled:function isEnabled(typeIdentifier,version){var portableElement=this.get(typeIdentifier,version);return portableElement&&!0===portableElement.enabled}}))}}),define("taoQtiItem/qtiCreator/helper/qtiElements",["jquery","lodash","i18n","services/features"],function($,_,__,featuresService){"use strict";const QtiElements={classes:{itemBody:{parents:["bodyElement"],contains:["block"],abstract:!0},atomicBlock:{parents:["blockStatic","bodyElement","flowStatic"],contains:["inline"],abstract:!0},atomicInline:{parents:["bodyElement","flowStatic","inlineStatic"],contains:["inline"],abstract:!0},simpleBlock:{parents:["blockStatic","bodyElement","flowStatic"],contains:["block"],abstract:!0},simpleInline:{parents:["bodyElement","flowStatic","inlineStatic"],contains:["inline"],abstract:!0},flowStatic:{parents:["flow"],abstract:!0},blockStatic:{parents:["block"],abstract:!0},inlineStatic:{parents:["inline"],abstract:!0},flow:{parents:["objectFlow"],abstract:!0},tableCell:{parents:["bodyElement"],contains:["flow"],abstract:!0},caption:{parents:["bodyElement"],contains:["inline"],xhtml:!0},col:{parents:["bodyElement"],xhtml:!0},colgroup:{parents:["bodyElement"],contains:["col"],xhtml:!0},div:{parents:["blockStatic","bodyElement","flowStatic"],contains:["flow"],xhtml:!0},dl:{parents:["blockStatic","bodyElement","flowStatic"],contains:["dlElement"],xhtml:!0},dt:{parents:["dlElement"],contains:["inline"],xhtml:!0},dd:{parents:["dlElement"],contains:["flow"],xhtml:!0},hr:{parents:["blockStatic","bodyElement","flowStatic"],xhtml:!0},math:{parents:["blockStatic","flowStatic","inlineStatic"],xhtml:!0},li:{parents:["bodyElement"],contains:["flow"],xhtml:!0},ol:{parents:["blockStatic","bodyElement","flowStatic"],contains:["li"],xhtml:!0},ul:{parents:["blockStatic","bodyElement","flowStatic"],contains:["li"],xhtml:!0},object:{parents:["bodyElement","flowStatic","inlineStatic"],contains:["objectFlow"],xhtml:!0},param:{parents:["objectFlow"],xhtml:!0},table:{parents:["blockStatic","bodyElement","flowStatic"],contains:["caption","col","colgroup","thead","tfoot","tbody"],xhtml:!0},tbody:{parents:["bodyElement"],contains:["tr"],xhtml:!0},tfoot:{parents:["bodyElement"],contains:["tr"],xhtml:!0},thead:{parents:["bodyElement"],contains:["tr"],xhtml:!0},td:{parents:["tableCell"],xhtml:!0},th:{parents:["tableCell"],xhtml:!0},tr:{parents:["bodyElement"],contains:["tableCell"],xhtml:!0},a:{parents:["simpleInline"],xhtml:!0},abbr:{parents:["simpleInline"],xhtml:!0},acronym:{parents:["simpleInline"],xhtml:!0},b:{parents:["simpleInline"],xhtml:!0},big:{parents:["simpleInline"],xhtml:!0},cite:{parents:["simpleInline"],xhtml:!0},code:{parents:["simpleInline"],xhtml:!0},dfn:{parents:["simpleInline"],xhtml:!0},em:{parents:["simpleInline"],xhtml:!0},i:{parents:["simpleInline"],xhtml:!0},kbd:{parents:["simpleInline"],xhtml:!0},q:{parents:["simpleInline"],xhtml:!0},samp:{parents:["simpleInline"],xhtml:!0},small:{parents:["simpleInline"],xhtml:!0},span:{parents:["simpleInline"],xhtml:!0},strong:{parents:["simpleInline"],xhtml:!0},sub:{parents:["simpleInline"],xhtml:!0},sup:{parents:["simpleInline"],xhtml:!0},tt:{parents:["simpleInline"],xhtml:!0},var:{parents:["simpleInline"],xhtml:!0},blockquote:{parents:["simpleBlock"],xhtml:!0},address:{parents:["atomicBlock"],xhtml:!0},h1:{parents:["atomicBlock"],xhtml:!0},h2:{parents:["atomicBlock"],xhtml:!0},h3:{parents:["atomicBlock"],xhtml:!0},h4:{parents:["atomicBlock"],xhtml:!0},h5:{parents:["atomicBlock"],xhtml:!0},h6:{parents:["atomicBlock"],xhtml:!0},p:{parents:["atomicBlock"],xhtml:!0},pre:{parents:["atomicBlock"],xhtml:!0},img:{parents:["atomicInline"],xhtml:!0,attributes:["src","alt","longdesc","height","width"]},br:{parents:["atomicInline"],xhtml:!0},infoControl:{parents:["blockStatic","bodyElement","flowStatic"],qti:!0},textRun:{parents:["inlineStatic","flowstatic"],qti:!0},feedbackInline:{parents:["simpleInline","feedbackElement"],qti:!0},feedbackBlock:{parents:["simpleBlock"],qti:!0},rubricBlock:{parents:["simpleBlock"],qti:!0},blockInteraction:{parents:["block","flow","interaction"],qti:!0},inlineInteraction:{parents:["inline","flow","interaction"],qti:!0},gap:{parents:["inlineStatic"],qti:!0},hottext:{parents:["flowstatic","inlineStatic"],contains:["inlineStatic"],qti:!0},printedVariable:{parents:["bodyElement","flowStatic","inlineStatic","textOrVariable"],qti:!0},prompt:{parents:["bodyElement"],contains:["inlineStatic"],qti:!0},templateElement:{parents:["bodyElement"],qti:!0},templateBlock:{parents:["blockStatic","flowStatic","templateElement"],contains:["blockStatic"],qti:!0},templateInline:{parents:["inlineStatic","flowStatic","templateElement"],contains:["inlineStatic"],qti:!0},choiceInteraction:{parents:["blockInteraction"],qti:!0},associateInteraction:{parents:["blockInteraction"],qti:!0},orderInteraction:{parents:["blockInteraction"],qti:!0},matchInteraction:{parents:["blockInteraction"],qti:!0},hottextInteraction:{parents:["blockInteraction"],qti:!0},gapMatchInteraction:{parents:["blockInteraction"],qti:!0},mediaInteraction:{parents:["blockInteraction"],qti:!0},sliderInteraction:{parents:["blockInteraction"],qti:!0},uploadInteraction:{parents:["blockInteraction"],qti:!0},drawingInteraction:{parents:["blockInteraction"],qti:!0},graphicInteraction:{parents:["blockInteraction"],qti:!0},hotspotInteraction:{parents:["graphicInteraction"],qti:!0},graphicAssociateInteraction:{parents:["graphicInteraction"],qti:!0},graphicOrderInteraction:{parents:["graphicInteraction"],qti:!0},graphicGapMatchInteraction:{parents:["graphicInteraction"],qti:!0},selectPointInteraction:{parents:["graphicInteraction"],qti:!0},textEntryInteraction:{parents:["stringInteraction","inlineInteraction"],qti:!0},extendedTextInteraction:{parents:["stringInteraction","blockInteraction"],qti:!0},inlineChoiceInteraction:{parents:["inlineInteraction"],qti:!0},endAttemptInteraction:{parents:["inlineInteraction"],qti:!0},customInteraction:{parents:["block","flow","interaction"],qti:!0},_container:{parents:["block"],qti:!0}},cache:{containable:{},children:{},parents:{}},getAllowedContainersElements(qtiClass,$container){const classes=QtiElements.getAllowedContainers(qtiClass);let jqSelector="";for(let i in classes)classes[i].qti||(jqSelector+=`${classes[i]}, `);return jqSelector&&(jqSelector=jqSelector.substring(0,jqSelector.length-2)),$(jqSelector,$container?$container:$(document)).filter(":not([data-qti-type])")},getAllowedContainers(qtiClass){let ret;if(QtiElements.cache.containable[qtiClass])ret=QtiElements.cache.containable[qtiClass];else{ret=[];const parents=QtiElements.getParentClasses(qtiClass,!0);for(let aClass in QtiElements.classes){const model=QtiElements.classes[aClass];if(model.contains){const intersect=_.intersection(model.contains,parents);intersect.length&&(!model.abstract&&ret.push(aClass),ret=_.union(ret,QtiElements.getChildClasses(aClass,!0)))}}QtiElements.cache.containable[qtiClass]=ret}return ret},getAllowedContents(qtiClass,recursive,checked){let ret=[];checked=checked||{};const model=QtiElements.classes[qtiClass];if(model&&model.contains)for(let i in model.contains){const contain=model.contains[i];if(!checked[contain]){checked[contain]=!0,ret.push(contain);const children=QtiElements.getChildClasses(contain,!0);for(let j in children){const child=children[j];checked[child]||(checked[child]=!0,ret.push(child),recursive&&(ret=_.union(ret,QtiElements.getAllowedContents(child,!0,checked))))}recursive&&(ret=_.union(ret,QtiElements.getAllowedContents(contain,!0,checked)))}}const parents=QtiElements.getParentClasses(qtiClass,!0);for(let i in parents)ret=_.union(ret,QtiElements.getAllowedContents(parents[i],recursive,checked));return _.uniq(ret)},isAllowedClass(qtiContainerClass,qtiContentClass){const allowedClasses=QtiElements.getAllowedContents(qtiContainerClass);return 0<=_.indexOf(allowedClasses,qtiContentClass)},getParentClasses(qtiClass,recursive){let ret;if(recursive&&QtiElements.cache.parents[qtiClass])ret=QtiElements.cache.parents[qtiClass];else{if(ret=[],QtiElements.classes[qtiClass]&&(ret=QtiElements.classes[qtiClass].parents,recursive)){for(let i in ret)ret=_.union(ret,QtiElements.getParentClasses(ret[i],recursive));ret=_.uniq(ret)}QtiElements.cache.parents[qtiClass]=ret}return ret},getChildClasses(qtiClass,recursive,type){let ret;const cacheType=type?type:"all";if(recursive&&QtiElements.cache.children[qtiClass]&&QtiElements.cache.children[qtiClass][cacheType])ret=QtiElements.cache.children[qtiClass][cacheType];else{for(let aClass in ret=[],QtiElements.classes){const model=QtiElements.classes[aClass];0<=_.indexOf(model.parents,qtiClass)&&(type?model[type]&&ret.push(aClass):ret.push(aClass),recursive&&(ret=_.union(ret,QtiElements.getChildClasses(aClass,recursive,type))))}QtiElements.cache.children[qtiClass]||(QtiElements.cache.children[qtiClass]={}),QtiElements.cache.children[qtiClass][cacheType]=ret}return ret},isBlock(qtiClass){return QtiElements.is(qtiClass,"block")},isInline(qtiClass){return QtiElements.is(qtiClass,"inline")},is(qtiClass,topClass){if(qtiClass===topClass)return!0;else{const parents=QtiElements.getParentClasses(qtiClass,!0);return 0<=_.indexOf(parents,topClass)}},isVisible(qtiClass){return this.is(qtiClass,"customInteraction")?featuresService.isVisible(`taoQtiItem/creator/customInteraction/${qtiClass.replace(/Interaction$/,"").replace(/^customInteraction\./,"")}`):!this.is(qtiClass,"interaction")||featuresService.isVisible(`taoQtiItem/creator/interaction/${qtiClass.replace(/Interaction$/,"")}`)},getAvailableAuthoringElements(){const tagTitles={commonInteractions:__("Common Interactions"),inlineInteractions:__("Inline Interactions"),graphicInteractions:__("Graphic Interactions")},authoringElements={choiceInteraction:{label:__("Choice Interaction"),description:__("Select a single (radio buttons) or multiple (check boxes) responses among a set of choices."),icon:"icon-choice",short:__("Choice"),qtiClass:"choiceInteraction",tags:[tagTitles.commonInteractions,"mcq"],group:"common-interactions"},orderInteraction:{label:__("Order Interaction"),icon:"icon-order",description:__("Arrange a list of choices in the correct order."),short:__("Order"),qtiClass:"orderInteraction",tags:[tagTitles.commonInteractions,"ordering"],group:"common-interactions"},associateInteraction:{label:__("Associate Interaction"),icon:"icon-associate",description:__("Create pair(s) from a series of choices."),short:__("Associate"),qtiClass:"associateInteraction",tags:[tagTitles.commonInteractions,"association"],group:"common-interactions"},matchInteraction:{label:__("Match Interaction"),icon:"icon-match",description:__("Create association(s) between two sets of choices displayed in a table (row and column)."),short:__("Match"),qtiClass:"matchInteraction",tags:[tagTitles.commonInteractions,"association"],group:"common-interactions"},hottextInteraction:{label:__("Hottext Interaction"),icon:"icon-hottext",description:__("Select one or more text parts (hottext) within a text."),short:__("Hottext"),qtiClass:"hottextInteraction",tags:[tagTitles.commonInteractions,"text"],group:"common-interactions"},gapMatchInteraction:{label:__("Gap Match Interaction"),icon:"icon-gap-match",description:__(" Fill in the gaps in a text from a set of choices."),short:__("Gap Match"),qtiClass:"gapMatchInteraction",tags:[tagTitles.commonInteractions,"text","association"],group:"common-interactions"},sliderInteraction:{label:__("Slider Interaction"),icon:"icon-slider",description:__("Select a value within a numerical range."),short:__("Slider"),qtiClass:"sliderInteraction",tags:[tagTitles.commonInteractions,"special"],group:"common-interactions"},extendedTextInteraction:{label:__("Extended Text Interaction"),icon:"icon-extended-text",description:__("Collect open-ended information in one or more text area(s) (strings or numeric values)."),short:__("Extended Text"),qtiClass:"extendedTextInteraction",tags:[tagTitles.commonInteractions,"text"],group:"common-interactions"},uploadInteraction:{label:__("File Upload Interaction"),icon:"icon-upload",description:__("Upload a file (e.g. document, picture...) as a response."),short:__("File Upload"),qtiClass:"uploadInteraction",tags:[tagTitles.commonInteractions,"special"],group:"common-interactions"},mediaInteraction:{label:__("Media Interaction"),icon:"icon-media",description:__("Control the playing parameters (auto-start, loop) of a video or audio file and report the number of time it has been played."),short:__("Media"),qtiClass:"mediaInteraction",tags:[tagTitles.commonInteractions,"media"],group:"common-interactions"},_container:{label:__("Text Block"),icon:"icon-font",description:__("Block contains the content (stimulus) of the item such as text or image. It is also required for Inline Interactions."),short:__("Block"),qtiClass:"_container",tags:[tagTitles.inlineInteractions,"text"],group:"inline-interactions"},inlineChoiceInteraction:{label:__("Inline Choice Interaction"),icon:"icon-inline-choice",description:__("Select a choice from a drop-down list."),short:__("Inline Choice"),qtiClass:"inlineChoiceInteraction",tags:[tagTitles.inlineInteractions,"inline-interactions","mcq"],group:"inline-interactions"},textEntryInteraction:{label:__("Text Entry Interaction"),icon:"icon-text-entry",description:__("Collect open-ended information in a short text input (strings or numeric values)."),short:__("Text Entry"),qtiClass:"textEntryInteraction",tags:[tagTitles.inlineInteractions,"inline-interactions","text"],group:"inline-interactions"},endAttemptInteraction:{label:__("End Attempt Interaction"),icon:"icon-end-attempt",description:__("Trigger the end of the item attempt."),short:__("End Attempt"),qtiClass:"endAttemptInteraction",tags:[tagTitles.inlineInteractions,"inline-interactions","button","submit"],group:"inline-interactions"},hotspotInteraction:{label:__("Hotspot Interaction"),icon:"icon-hotspot",description:__("Select one or more areas (hotspots) displayed on an picture."),short:__("Hotspot"),qtiClass:"hotspotInteraction",tags:[tagTitles.graphicInteractions,"mcq"],group:"graphic-interactions"},graphicOrderInteraction:{label:__("Graphic Order Interaction"),icon:"icon-graphic-order",description:__("Order the areas (hotspots) displayed on a picture."),short:__("Order"),qtiClass:"graphicOrderInteraction",tags:[tagTitles.graphicInteractions,"ordering"],group:"graphic-interactions"},graphicAssociateInteraction:{label:__("Graphic Associate Interaction"),icon:"icon-graphic-associate",description:__("Create association(s) between areas (hotspots) displayed on a picture."),short:__("Associate"),qtiClass:"graphicAssociateInteraction",tags:[tagTitles.graphicInteractions,"association"],group:"graphic-interactions"},graphicGapMatchInteraction:{label:__("Graphic Gap Match Interaction"),icon:"icon-graphic-gap",description:__("Fill in the gaps on a picture with a set of image choices."),short:__("Gap Match"),qtiClass:"graphicGapMatchInteraction",tags:[tagTitles.graphicInteractions,"association"],group:"graphic-interactions"},selectPointInteraction:{label:__("Select Point Interaction"),icon:"icon-select-point",description:__("Position one or more points on a picture (response areas are not displayed)."),short:__("Select Point"),qtiClass:"selectPointInteraction",tags:[tagTitles.graphicInteractions],group:"graphic-interactions"}},availableElements={};for(const[elementId,element]of Object.entries(authoringElements))this.isVisible(elementId)&&(availableElements[elementId]=element);return availableElements}};return QtiElements}),define("taoQtiItem/portableElementRegistry/factory/ciRegistry",["lodash","i18n","taoQtiItem/portableElementRegistry/factory/factory","taoQtiItem/qtiCreator/helper/qtiElements"],function(_,__,portableElementRegistry,qtiElements){"use strict";return function customInteractionRegistry(){return portableElementRegistry({getAuthoringData(typeIdentifier){let options=1{const execStack=[];return _.forEach(plugins,plugin=>{_.isFunction(plugin[method])&&execStack.push(plugin[method]())}),Promise.all(execStack)},qtiCreatorContext={init(){return pluginLoader.load().then(()=>{const pluginFactories=pluginLoader.getPlugins();return _.forEach(pluginFactories,pluginFactory=>{const plugin=pluginFactory(this);plugins[plugin.getName()]=plugin}),pluginRun("init").then(()=>{this.trigger("init")})}).catch(err=>{this.trigger("error",err)})},destroy(){return pluginRun("destroy").then(()=>{this.trigger("destroy")}).catch(function(err){this.trigger("error",err)})}};return eventifier(qtiCreatorContext)}}),define("taoQtiItem/qtiItem/core/qtiClasses",[],function(){"use strict";var qtiClasses={_container:"taoQtiItem/qtiItem/core/Container",assessmentItem:"taoQtiItem/qtiItem/core/Item",responseProcessing:"taoQtiItem/qtiItem/core/ResponseProcessing",_simpleFeedbackRule:"taoQtiItem/qtiItem/core/response/SimpleFeedbackRule",stylesheet:"taoQtiItem/qtiItem/core/Stylesheet",math:"taoQtiItem/qtiItem/core/Math",figure:"taoQtiItem/qtiItem/core/Figure",img:"taoQtiItem/qtiItem/core/Img",figcaption:"taoQtiItem/qtiItem/core/Figcaption",object:"taoQtiItem/qtiItem/core/Object",outcomeDeclaration:"taoQtiItem/qtiItem/core/variables/OutcomeDeclaration",responseDeclaration:"taoQtiItem/qtiItem/core/variables/ResponseDeclaration",rubricBlock:"taoQtiItem/qtiItem/core/RubricBlock",associableHotspot:"taoQtiItem/qtiItem/core/choices/AssociableHotspot",gap:"taoQtiItem/qtiItem/core/choices/Gap",gapImg:"taoQtiItem/qtiItem/core/choices/GapImg",gapText:"taoQtiItem/qtiItem/core/choices/GapText",hotspotChoice:"taoQtiItem/qtiItem/core/choices/HotspotChoice",hottext:"taoQtiItem/qtiItem/core/choices/Hottext",inlineChoice:"taoQtiItem/qtiItem/core/choices/InlineChoice",simpleAssociableChoice:"taoQtiItem/qtiItem/core/choices/SimpleAssociableChoice",simpleChoice:"taoQtiItem/qtiItem/core/choices/SimpleChoice",associateInteraction:"taoQtiItem/qtiItem/core/interactions/AssociateInteraction",choiceInteraction:"taoQtiItem/qtiItem/core/interactions/ChoiceInteraction",endAttemptInteraction:"taoQtiItem/qtiItem/core/interactions/EndAttemptInteraction",extendedTextInteraction:"taoQtiItem/qtiItem/core/interactions/ExtendedTextInteraction",gapMatchInteraction:"taoQtiItem/qtiItem/core/interactions/GapMatchInteraction",graphicAssociateInteraction:"taoQtiItem/qtiItem/core/interactions/GraphicAssociateInteraction",graphicGapMatchInteraction:"taoQtiItem/qtiItem/core/interactions/GraphicGapMatchInteraction",graphicOrderInteraction:"taoQtiItem/qtiItem/core/interactions/GraphicOrderInteraction",hotspotInteraction:"taoQtiItem/qtiItem/core/interactions/HotspotInteraction",hottextInteraction:"taoQtiItem/qtiItem/core/interactions/HottextInteraction",inlineChoiceInteraction:"taoQtiItem/qtiItem/core/interactions/InlineChoiceInteraction",matchInteraction:"taoQtiItem/qtiItem/core/interactions/MatchInteraction",mediaInteraction:"taoQtiItem/qtiItem/core/interactions/MediaInteraction",orderInteraction:"taoQtiItem/qtiItem/core/interactions/OrderInteraction",prompt:"taoQtiItem/qtiItem/core/interactions/Prompt",selectPointInteraction:"taoQtiItem/qtiItem/core/interactions/SelectPointInteraction",sliderInteraction:"taoQtiItem/qtiItem/core/interactions/SliderInteraction",textEntryInteraction:"taoQtiItem/qtiItem/core/interactions/TextEntryInteraction",uploadInteraction:"taoQtiItem/qtiItem/core/interactions/UploadInteraction",feedbackBlock:"taoQtiItem/qtiItem/core/feedbacks/FeedbackBlock",feedbackInline:"taoQtiItem/qtiItem/core/feedbacks/FeedbackInline",modalFeedback:"taoQtiItem/qtiItem/core/feedbacks/ModalFeedback",customInteraction:"taoQtiItem/qtiItem/core/interactions/CustomInteraction",infoControl:"taoQtiItem/qtiItem/core/PortableInfoControl",include:"taoQtiItem/qtiItem/core/Include",table:"taoQtiItem/qtiItem/core/Table",printedVariable:"taoQtiItem/qtiItem/core/PrintedVariable",_tooltip:"taoQtiItem/qtiItem/core/Tooltip"};return qtiClasses}),define("taoQtiItem/qtiItem/helper/util",["lodash"],function(_){"use strict";_=_&&Object.prototype.hasOwnProperty.call(_,"default")?_["default"]:_;var util={buildSerial:function buildSerial(prefix){for(var id=prefix||"",chars="abcdefghijklmnopqrstuvwxyz0123456789",i=0;22>i;i++)id+=chars.charAt(Math.floor(Math.random()*chars.length));return id},buildId:function buildId(item,prefix){var i=1,suffix="",exists=!1,id,usedIds;if(!item)throw new TypeError("A item is required to generate a unique identifier");usedIds=item.getUsedIds();do exists=!1,id=prefix+suffix,usedIds&&usedIds.includes(id)&&(exists=!0,suffix="_"+i,i++);while(exists);return id},buildIdentifier:function buildIdentifier(item,prefix,useSuffix){var suffix="",i=1,exists=!1,id,usedIds;if(!item)throw new TypeError("A item is required to generate a unique identifier");if(!prefix)throw new TypeError("Prefix is required to build an identifier");usedIds=item.getUsedIdentifiers(),useSuffix=!!_.isUndefined(useSuffix)||useSuffix,prefix&&(prefix=prefix.replace(/_[0-9]+$/gi,"_").replace(/[^a-zA-Z0-9_]/gi,"_").replace(/(_)+/gi,"_"),useSuffix&&(suffix="_"+i));do exists=!1,id=prefix+suffix,usedIds[id]&&(exists=!0,suffix="_"+i,i++);while(exists);return id},findInCollection:function findInCollection(element,collectionNames,searchedSerial){var found=null;if(_.isString(collectionNames)&&(collectionNames=[collectionNames]),_.isArray(collectionNames))_.forEach(collectionNames,function(collectionName){var collection=element;_.forEach(collectionName.split("."),function(nameToken){collection=collection[nameToken]});var elt=collection[searchedSerial];return elt?(found={parent:element,element:elt},!1):(_.forEach(collection,function(elt){if(_.isFunction(elt.find)&&(found=elt.find(searchedSerial),found))return!1}),!found)&&void 0});else throw new Error("invalid argument : collectionNames must be an array or a string");return found},addMarkupNamespace:function addMarkupNamespace(markup,ns){return ns?(markup=markup.replace(/<(\/)?([a-z:]+)(\s?)([^><]*)>/g,function($0,$1,$2,$3,$4){return 0<$2.indexOf(":")?$0:($1=$1||"",$3=$3||"","<"+$1+ns+":"+$2+$3+$4+">")}),markup):markup},removeMarkupNamespaces:function removeMarkupNamespace(markup){return markup.replace(/<(\/)?(\w*):([^>]*)>/g,"<$1$3>")},getMarkupUsedNamespaces:function getMarkupUsedNamespaces(markup){var namespaces=[];return markup.replace(/<(\/)?(\w*):([^>]*)>/g,function(original,slash,ns,node){return namespaces.push(ns),"<"+slash+node+">"}),_.uniq(namespaces)}};return util}),define("taoQtiItem/qtiItem/helper/rendererConfig",["lodash","jquery"],function(_,$){"use strict";_=_&&Object.prototype.hasOwnProperty.call(_,"default")?_["default"]:_,$=$&&Object.prototype.hasOwnProperty.call($,"default")?$["default"]:$;var rendererConfigHelper={getOptionsFromArguments:function(args){var options={data:{},placeholder:null,subclass:"",renderer:null};return _.forEach(args,function(arg){arg&&(arg.isRenderer?options.renderer=arg:arg instanceof $&&arg.length?options.placeholder=arg:_.isString(arg)?options.subclass=arg:_.isPlainObject(arg)?options.data=arg:console.log("invalid arg",arg,args))}),options}};return rendererConfigHelper}),define("taoQtiItem/qtiItem/core/Element",["jquery","lodash","class","core/logger","taoQtiItem/qtiItem/helper/util","taoQtiItem/qtiItem/helper/rendererConfig"],function($,_,_class,loggerFactory,util,rendererConfig){"use strict";$=$&&Object.prototype.hasOwnProperty.call($,"default")?$["default"]:$,_=_&&Object.prototype.hasOwnProperty.call(_,"default")?_["default"]:_,loggerFactory=loggerFactory&&Object.prototype.hasOwnProperty.call(loggerFactory,"default")?loggerFactory["default"]:loggerFactory,util=util&&Object.prototype.hasOwnProperty.call(util,"default")?util["default"]:util,rendererConfig=rendererConfig&&Object.prototype.hasOwnProperty.call(rendererConfig,"default")?rendererConfig["default"]:rendererConfig;var _instances={},logger=loggerFactory("taoQtiItem/qtiItem/core/Element"),Element=Class.extend({qtiClass:"",serial:"",rootElement:null,init:function(serial,attributes){if(this.attributes={},this.metaData={},"object"==typeof serial&&(attributes=serial,serial=""),serial||(serial=util.buildSerial(this.qtiClass+"_")),serial&&("string"!=typeof serial||!serial.match(/^[a-z_0-9]*$/i)))throw"invalid QTI serial : ("+typeof serial+") "+serial;if(!_instances[serial])_instances[serial]=this,this.serial=serial,this.setAttributes(attributes||{});else throw"a QTI Element with the same serial already exists "+serial;"function"==typeof this.initContainer&&this.initContainer(arguments[2]||""),"function"==typeof this.initObject&&this.initObject()},is:function(qtiClass){return qtiClass===this.qtiClass},placeholder:function(){return"{{"+this.serial+"}}"},getSerial:function(){return this.serial},getUsedIdentifiers:function(){var usedIds={},elts=this.getComposingElements();for(var i in elts){var elt=elts[i],id=elt.attr("identifier");id&&(usedIds[id]=elt)}return usedIds},getUsedIds:function getUsedIds(){var usedIds=[];return _.forEach(this.getComposingElements(),function(elt){var id=elt.attr("id");id&&!usedIds.includes(id)&&usedIds.push(id)}),usedIds},attr:function(name,value){if(name)if(void 0!==value)this.attributes[name]=value;else if("object"==typeof name)for(var prop in name)this.attr(prop,name[prop]);else if("string"==typeof name)return void 0===this.attributes[name]?void 0:this.attributes[name];return this},data:function(name,value){if(name)if(void 0!==value)this.metaData[name]=value,$(document).trigger("metaChange.qti-widget",{element:this,key:name,value:value});else if("object"==typeof name)for(var prop in name)this.data(prop,name[prop]);else if("string"==typeof name)return void 0===this.metaData[name]?void 0:this.metaData[name];return this},removeData:function(name){return delete this.metaData[name],this},removeAttr:function(name){return this.removeAttributes(name)},setAttributes:function(attributes){return _.isPlainObject(attributes)||logger.warn("attributes should be a plain object"),this.attributes=attributes,this},getAttributes:function(){return _.clone(this.attributes)},removeAttributes:function(attrNames){for(var i in"string"==typeof attrNames&&(attrNames=[attrNames]),attrNames)delete this.attributes[attrNames[i]];return this},getComposingElements:function(){function append(element){elts[element.getSerial()]=element,elts=_.extend(elts,element.getComposingElements())}var elts={};return"function"==typeof this.initContainer&&append(this.getBody()),"function"==typeof this.initObject&&append(this.getObject()),_.forEach(this.metaData,function(v){Element.isA(v,"_container")?append(v):_.isArray(v)&&_.forEach(v,function(v){Element.isA(v,"_container")&&append(v)})}),elts},getUsedClasses:function(){var ret=[this.qtiClass],composingElts=this.getComposingElements();return _.forEach(composingElts,function(elt){ret.push(elt.qtiClass)}),_.uniq(ret)},find:function(serial){var found=null,object,body;return"function"==typeof this.initObject&&(object=this.getObject(),object.serial===serial&&(found={parent:this,element:object,location:"object"})),found||"function"!=typeof this.initContainer||(body=this.getBody(),found=body.serial===serial?{parent:this,element:body,location:"body"}:this.getBody().find(serial,this)),found},parent:function(){var item=this.getRootElement();if(item){var found=item.find(this.getSerial());if(found)return found.parent}return null},setRelatedItem:function(item){logger.warn("Deprecated use of setRelatedItem()"),this.setRootElement(item)},setRootElement:function(item){var composingElts,i;if(Element.isA(item,"assessmentItem"))for(i in this.rootElement=item,composingElts=this.getComposingElements(),composingElts)composingElts[i].setRootElement(item)},getRelatedItem:function(){return logger.warn("Deprecated use of getRelatedItem()"),this.getRootElement()},getRootElement:function(){var ret=null;return Element.isA(this.rootElement,"assessmentItem")&&(ret=this.rootElement),ret},setRenderer:function(renderer){if(renderer&&renderer.isRenderer){this.renderer=renderer;var elts=this.getComposingElements();for(var serial in elts)elts[serial].setRenderer(renderer)}else throw"invalid qti rendering engine"},getRenderer:function(){return this.renderer},render:function render(){var args=rendererConfig.getOptionsFromArguments(arguments),_renderer=args.renderer||this.getRenderer(),tplData={},defaultData={tag:this.qtiClass,serial:this.serial,attributes:this.getAttributes()},rendering;if(!_renderer)throw new Error("render: no renderer found for the element "+this.qtiClass+":"+this.serial);return"function"==typeof this.initContainer&&(defaultData.body=this.getBody().render(args.renderer)),"function"==typeof this.initObject&&(defaultData.object={attributes:this.object.getAttributes()},defaultData.object.attributes.data=_renderer.resolveUrl(this.object.attr("data"))),tplData=_.merge(defaultData,args.data||{}),tplData=_renderer.getData(this,tplData,args.subclass),rendering=_renderer.renderTpl(this,tplData,args.subclass),args.placeholder&&args.placeholder.replaceWith(rendering),rendering},postRender:function(data,altClassName,renderer){var postRenderers=[],_renderer=renderer||this.getRenderer();if("function"==typeof this.initContainer&&(postRenderers=this.getBody().postRender(data,"",renderer)),_renderer)postRenderers.push(_renderer.postRender(this,data,altClassName));else throw"postRender: no renderer found for the element "+this.qtiClass+":"+this.serial;return _.compact(postRenderers)},getContainer:function($scope,subclass){var renderer=this.getRenderer();if(renderer)return renderer.getContainer(this,$scope,subclass);throw"getContainer: no renderer found for the element "+this.qtiClass+":"+this.serial},toArray:function(){var arr={serial:this.serial,type:this.qtiClass,attributes:this.getAttributes()};return"function"==typeof this.initContainer&&(arr.body=this.getBody().toArray()),"function"==typeof this.initObject&&(arr.object=this.object.toArray()),arr},isEmpty:function(){return!1},addClass:function(className){var clazz=this.attr("class")||"";_containClass(clazz,className)||this.attr("class",clazz+(clazz.length?" ":"")+className)},hasClass:function(className){return _containClass(this.attr("class"),className)},removeClass:function(className){var clazz=this.attr("class")||"";if(clazz){var regex=new RegExp("(?:^|\\s)"+className+"(?:\\s|$)");clazz=clazz.replace(regex," ").trim(),clazz?this.attr("class",clazz):this.removeAttr("class")}},toggleClass:function(className,state){return"boolean"==typeof state?state?this.addClass(className):this.removeClass(className):this.hasClass(className)?this.removeClass(className):this.addClass(className)},isset:function(){return Element.issetElement(this.serial)},unset:function(){return Element.unsetElement(this.serial)}}),_containClass=function(allClassStr,className){var regex=new RegExp("(?:^|\\s)"+className+"(?:\\s|$)","");return allClassStr&®ex.test(allClassStr)};return Element.isA=function(qtiElement,qtiClass){return qtiElement instanceof Element&&qtiElement.is(qtiClass)},Element.getElementBySerial=function(serial){return _instances[serial]},Element.issetElement=function(serial){return!!_instances[serial]},Element.unsetElement=function(serial){var element=Element.getElementBySerial(serial);if(element){var composingElements=element.getComposingElements();return _.forEach(composingElements,function(elt){delete _instances[elt.serial]}),delete _instances[element.serial],!0}return!1},Element}),define("taoQtiItem/qtiItem/helper/xmlNsHandler",[],function(){"use strict";function getPrefix(namespaces,html5Ns){let key;for(key in namespaces)if(namespaces[key]===html5Ns)return key;return"qh5"}const prefixed="article|aside|bdi|figure|footer|header|nav|rb|rp|rt|rtc|ruby|section";var xmlNsHandler={stripNs:function stripNs(body){const pattern="([\\w]+)\\:(article|aside|bdi|figure|footer|header|nav|rb|rp|rt|rtc|ruby|section)",openRegEx=/(<([\w]+)\:(article|aside|bdi|figure|footer|header|nav|rb|rp|rt|rtc|ruby|section))/gi,closeRegEx=/(<\/([\w]+)\:(article|aside|bdi|figure|footer|header|nav|rb|rp|rt|rtc|ruby|section)>)/gi;return body.replace(openRegEx,"<$3").replace(closeRegEx,"")},restoreNs:function restoreNs(xml,namespaces){let ignoreMarkup=!!(2)+)/gim,""));const xmlRe=/(<(article|aside|bdi|figure|footer|header|nav|rb|rp|rt|rtc|ruby|section)[^>]*>|<\/(article|aside|bdi|figure|footer|header|nav|rb|rp|rt|rtc|ruby|section)>)/gi,tagRe=/((<)(article|aside|bdi|figure|footer|header|nav|rb|rp|rt|rtc|ruby|section)([^>]*)(>)|(<\/)(article|aside|bdi|figure|footer|header|nav|rb|rp|rt|rtc|ruby|section)(>))/i,xmlMatch=xmlCopy.match(xmlRe),imsXsd="http://www.imsglobal.org/xsd",html5Ns=imsXsd+"/imsqtiv2p2_html5_v1p0",prefix=getPrefix(namespaces,html5Ns),prefixAtt="xmlns:"+prefix+"=\""+html5Ns+"\"";let i=xmlMatch?xmlMatch.length:0;if(!xmlMatch)return xml;for(;i--;){const tagMatch=xmlMatch[i].match(tagRe);xml=xml.replace(xmlMatch[i],tagMatch[5]?"<"+prefix+":"+tagMatch[3]+tagMatch[4]+">":"")}return xmlMatch.length&&-1===xml.indexOf(prefixAtt)&&(xml=xml.replace("({qtiClass:"responseCondition",responseIf:{qtiClass:"responseIf",expression:{qtiClass:"match",expressions:[{qtiClass:"variable",attributes:{identifier:responseIdentifier}},{qtiClass:"correct",attributes:{identifier:responseIdentifier}}]},responseRules:[{qtiClass:"setOutcomeValue",attributes:{identifier:outcomeIdentifier},expression:{qtiClass:"sum",expressions:[{qtiClass:"variable",attributes:{identifier:outcomeIdentifier}},{qtiClass:"baseValue",attributes:{baseType:"integer"},value:"1"}]}}]}}),MAP_RESPONSE:(responseIdentifier,outcomeIdentifier)=>({qtiClass:"responseCondition",responseIf:{qtiClass:"responseIf",expression:{qtiClass:"not",expressions:[{qtiClass:"isNull",expressions:[{qtiClass:"variable",attributes:{identifier:responseIdentifier}}]}]},responseRules:[{qtiClass:"setOutcomeValue",attributes:{identifier:outcomeIdentifier},expression:{qtiClass:"sum",expressions:[{qtiClass:"variable",attributes:{identifier:outcomeIdentifier}},{qtiClass:"mapResponse",attributes:{identifier:responseIdentifier}}]}}]}}),MAP_RESPONSE_POINT:(responseIdentifier,outcomeIdentifier)=>({qtiClass:"responseCondition",responseIf:{qtiClass:"responseIf",expression:{qtiClass:"not",expressions:[{qtiClass:"isNull",expressions:[{qtiClass:"variable",attributes:{identifier:responseIdentifier}}]}]},responseRules:[{qtiClass:"setOutcomeValue",attributes:{identifier:outcomeIdentifier},expression:{qtiClass:"sum",expressions:[{qtiClass:"variable",attributes:{identifier:outcomeIdentifier}},{qtiClass:"mapResponsePoint",attributes:{identifier:responseIdentifier}}]}}]}})};return responseRules}),define("taoQtiItem/qtiItem/helper/response",["lodash","taoQtiItem/qtiItem/helper/responseRules"],function(_,responseRulesHelper){"use strict";_=_&&Object.prototype.hasOwnProperty.call(_,"default")?_["default"]:_,responseRulesHelper=responseRulesHelper&&Object.prototype.hasOwnProperty.call(responseRulesHelper,"default")?responseRulesHelper["default"]:responseRulesHelper;const _templateNames={MATCH_CORRECT:"http://www.imsglobal.org/question/qti_v2p1/rptemplates/match_correct",MAP_RESPONSE:"http://www.imsglobal.org/question/qti_v2p1/rptemplates/map_response",MAP_RESPONSE_POINT:"http://www.imsglobal.org/question/qti_v2p1/rptemplates/map_response_point",NONE:"no_response_processing"};var response={isUsingTemplate(response,tpl){return!!(_.isString(tpl)&&(tpl===response.template||_templateNames[tpl]===response.template))},isValidTemplateName(tplName){return!!this.getTemplateUriFromName(tplName)},getTemplateUriFromName(tplName){return _templateNames[tplName]||""},getTemplateNameFromUri(tplUri){let tplName="";return _.forIn(_templateNames,(uri,name)=>{if(uri===tplUri)return tplName=name,!1}),tplName},getTemplateNameFromResponseRules(responseIdentifier,responseRules){if(!responseRules)return"NONE";const{responseIf:{responseRules:[outcomeRules={}]=[]}={}}=responseRules,{attributes:{identifier:outcomeIdentifier}={}}=outcomeRules;return outcomeIdentifier?Object.keys(responseRulesHelper).find(key=>_.isEqual(responseRules,responseRulesHelper[key](responseIdentifier,outcomeIdentifier))):""}};return response}),define("taoQtiItem/qtiItem/helper/itemScore",[],function(){"use strict";var itemScore=responseIdentifiers=>{const outcomeExpressions=responseIdentifiers.map(outcomeIdentifier=>({qtiClass:"variable",attributes:{identifier:`SCORE_${outcomeIdentifier}`}}));return{qtiClass:"setOutcomeValue",attributes:{identifier:"SCORE"},expression:{qtiClass:"sum",expressions:outcomeExpressions}}};return itemScore}),define("taoQtiItem/qtiItem/core/Loader",["lodash","class","taoQtiItem/qtiItem/core/qtiClasses","taoQtiItem/qtiItem/core/Element","taoQtiItem/qtiItem/helper/xmlNsHandler","core/moduleLoader","taoQtiItem/qtiItem/helper/response","taoQtiItem/qtiItem/helper/itemScore"],function(_,_class,qtiClasses,Element,xmlNsHandler,moduleLoader,responseHelper,itemScoreHelper){"use strict";_=_&&Object.prototype.hasOwnProperty.call(_,"default")?_["default"]:_,qtiClasses=qtiClasses&&Object.prototype.hasOwnProperty.call(qtiClasses,"default")?qtiClasses["default"]:qtiClasses,Element=Element&&Object.prototype.hasOwnProperty.call(Element,"default")?Element["default"]:Element,xmlNsHandler=xmlNsHandler&&Object.prototype.hasOwnProperty.call(xmlNsHandler,"default")?xmlNsHandler["default"]:xmlNsHandler,moduleLoader=moduleLoader&&Object.prototype.hasOwnProperty.call(moduleLoader,"default")?moduleLoader["default"]:moduleLoader,responseHelper=responseHelper&&Object.prototype.hasOwnProperty.call(responseHelper,"default")?responseHelper["default"]:responseHelper,itemScoreHelper=itemScoreHelper&&Object.prototype.hasOwnProperty.call(itemScoreHelper,"default")?itemScoreHelper["default"]:itemScoreHelper;const loadPortableCustomElementProperties=(portableElement,rawProperties)=>{var properties={};_.forOwn(rawProperties,(value,key)=>{try{properties[key]=JSON.parse(value)}catch(e){properties[key]=value}}),portableElement.properties=properties},loadPortableCustomElementData=(portableElement,data)=>{portableElement.typeIdentifier=data.typeIdentifier,portableElement.markup=data.markup,portableElement.entryPoint=data.entryPoint,portableElement.libraries=data.libraries,portableElement.setNamespace("",data.xmlns),loadPortableCustomElementProperties(portableElement,data.properties)};var Loader=Class.extend({init(item,classesLocation){this.qti={},this.classesLocation={},this.item=item||null,this.setClassesLocation(classesLocation||qtiClasses)},setClassesLocation(qtiClassesList){return _.extend(this.classesLocation,qtiClassesList),this},getRequiredClasses(data){let ret=[];for(let i in data)"qtiClass"==i&&"_container"!==data[i]&&"rootElement"!==i?ret.push(data[i]):"object"==typeof data[i]&&"responseRules"!==i&&(ret=_.union(ret,this.getRequiredClasses(data[i])));return ret},loadRequiredClasses(data,callback,reload){let requiredClass;const requiredClasses=this.getRequiredClasses(data,reload),required=[];for(let i in requiredClasses)if(requiredClass=requiredClasses[i],this.classesLocation[requiredClass])required.push({module:this.classesLocation[requiredClass],category:"qti"});else throw new Error(`missing qti class location declaration : ${requiredClass}`);moduleLoader([],()=>!0).addList(required).load().then(loadeded=>{loadeded.forEach(QtiClass=>{this.qti[QtiClass.prototype.qtiClass]=QtiClass}),callback.call(this,this.qti)})},getLoadedClasses(){return _.keys(this.qti)},loadItemData(data,callback){this.loadRequiredClasses(data,Qti=>{if("object"==typeof data&&"assessmentItem"===data.qtiClass){for(let i in data.serial&&Element.unsetElement(data.serial),this.item=new Qti.assessmentItem(data.serial,data.attributes||{}),this.loadContainer(this.item.getBody(),data.body),data.outcomes){const outcome=this.buildOutcome(data.outcomes[i]);outcome&&this.item.addOutcomeDeclaration(outcome)}for(let i in data.feedbacks){const feedback=this.buildElement(data.feedbacks[i]);feedback&&this.item.addModalFeedback(feedback)}for(let i in data.stylesheets){const stylesheet=this.buildElement(data.stylesheets[i]);stylesheet&&this.item.addStylesheet(stylesheet)}let responseRules=data.responseProcessing&&data.responseProcessing.responseRules?[...data.responseProcessing.responseRules]:[];for(let i in data.responses){const responseIdentifier=data.responses[i].identifier,responseRuleItemIndex=responseRules.findIndex(_ref=>{let{responseIf:{expression:{expressions:[expression={}]=[]}={}}={}}=_ref;return expression.attributes&&expression.attributes.identifier===responseIdentifier||expression.expressions&&expression.expressions[0]&&expression.expressions[0].attributes&&expression.expressions[0].attributes.identifier===responseIdentifier}),[responseRule]=-1===responseRuleItemIndex?[]:responseRules.splice(responseRuleItemIndex,1),response=this.buildResponse(data.responses[i],responseRule);if(response){this.item.addResponseDeclaration(response);const feedbackRules=data.responses[i].feedbackRules;feedbackRules&&_.forIn(feedbackRules,(fbData,serial)=>{const{attributes:{identifier:feedbackOutcomeIdentifier}={}}=data.outcomes[fbData.feedbackOutcome]||{};response.feedbackRules[serial]=this.buildSimpleFeedbackRule(fbData,response);const feedbackResponseRuleIndex=responseRules.findIndex(_ref2=>{let{responseIf:{responseRules:[setOutcomeResponseRule={}]=[]}={}}=_ref2;const{attributes={},qtiClass}=setOutcomeResponseRule,outcomeIdentifier=attributes.identifier;return feedbackOutcomeIdentifier===outcomeIdentifier&&"setOutcomeValue"===qtiClass});-1!==feedbackResponseRuleIndex&&responseRules.splice(feedbackResponseRuleIndex,1)})}}const responseIdentifiers=Object.keys(this.item.responses||{}).map(responseKey=>this.item.responses[responseKey].attributes.identifier);if(data.responseProcessing){const customResponseProcessing=0!this.item.responses[responseKey].template);this.item.setResponseProcessing(this.buildResponseProcessing(data.responseProcessing,customResponseProcessing))}this.item.setNamespaces(data.namespaces),this.item.setSchemaLocations(data.schemaLocations),this.item.setApipAccessibility(data.apipAccessibility)}"function"==typeof callback&&callback.call(this,this.item)})},loadAndBuildElement(data,callback){this.loadRequiredClasses(data,()=>{const element=this.buildElement(data);"function"==typeof callback&&callback.call(this,element)})},loadElement(element,data,callback){this.loadRequiredClasses(data,()=>{this.loadElementData(element,data),"function"==typeof callback&&callback.call(this,element)})},loadElements(data,callback){if(!this.item)throw new Error("QtiLoader : cannot load elements in empty item");this.loadRequiredClasses(data,()=>{const allElements=this.item.getComposingElements();for(let i in data){const elementData=data[i];elementData&&elementData.qtiClass&&elementData.serial&&allElements[elementData.serial]&&this.loadElementData(allElements[elementData.serial],elementData)}"function"==typeof callback&&callback.call(this,this.item)})},buildResponse(data,responseRule){const response=this.buildElement(data);return response.template=responseHelper.getTemplateUriFromName(responseHelper.getTemplateNameFromResponseRules(data.identifier,responseRule))||data.howMatch||null,response.defaultValue=data.defaultValue||null,response.correctResponse=data.correctResponses||null,response.mapEntries=_.size(data.mapping)?data.mapping:_.size(data.areaMapping)?data.areaMapping:{},response.mappingAttributes=data.mappingAttributes||{},response},buildSimpleFeedbackRule(data,response){const feedbackRule=this.buildElement(data);feedbackRule.setCondition(response,data.condition,data.comparedValue||null),feedbackRule.feedbackOutcome=this.item.outcomes[data.feedbackOutcome]||null,feedbackRule.feedbackThen=this.item.modalFeedbacks[data.feedbackThen]||null,feedbackRule.feedbackElse=this.item.modalFeedbacks[data.feedbackElse]||null;const comparedOutcome=feedbackRule.comparedOutcome;return feedbackRule.feedbackThen&&feedbackRule.feedbackThen.data("relatedResponse",comparedOutcome),feedbackRule.feedbackElse&&feedbackRule.feedbackElse.data("relatedResponse",comparedOutcome),feedbackRule},buildOutcome(data){const outcome=this.buildElement(data);return outcome.defaultValue=data.defaultValue||null,outcome},buildResponseProcessing(data,customResponseProcessing){const rp=this.buildElement(data);return customResponseProcessing?(rp.xml=data.data,rp.processingType="custom"):rp.processingType="templateDriven",rp},loadContainer(bodyObject,bodyData){if(!Element.isA(bodyObject,"_container"))throw new Error("bodyObject must be a QTI Container");if(!(bodyData&&"string"==typeof bodyData.body&&"object"==typeof bodyData.elements))throw new Error("wrong bodydata format");const attributes=_.defaults(bodyData.attributes||{},bodyObject.attributes||{});for(let serial in bodyObject.setAttributes(attributes),bodyData.elements){const eltData=bodyData.elements[serial],element=this.buildElement(eltData);element&&bodyObject.setElement(element,bodyData.body)}bodyObject.body(xmlNsHandler.stripNs(bodyData.body))},buildElement(elementData){if(!(elementData&&elementData.qtiClass&&elementData.serial))throw new Error("wrong elementData format");const className=elementData.qtiClass;if(!this.qti[className])throw new Error(`the qti element class does not exist: ${className}`);const elt=new this.qti[className](elementData.serial);return this.loadElementData(elt,elementData),elt},loadElementData(element,data){const attributes=_.defaults(data.attributes||{},element.attributes||{});element.setAttributes(attributes);let body=data.body;return!body&&data.text&&"inlineChoice"===data.qtiClass&&(body={body:data.text,elements:{}}),element.body&&body&&element.bdy&&this.loadContainer(element.getBody(),body),element.object&&data.object&&element.object&&this.loadObjectData(element.object,data.object),Element.isA(element,"interaction")?this.loadInteractionData(element,data):Element.isA(element,"choice")?this.loadChoiceData(element,data):Element.isA(element,"math")?this.loadMathData(element,data):Element.isA(element,"infoControl")?this.loadPicData(element,data):Element.isA(element,"_tooltip")&&this.loadTooltipData(element,data),element},loadInteractionData(interaction,data){Element.isA(interaction,"blockInteraction")&&data.prompt&&this.loadContainer(interaction.prompt.getBody(),data.prompt),this.buildInteractionChoices(interaction,data),Element.isA(interaction,"customInteraction")&&this.loadPciData(interaction,data)},buildInteractionChoices(interaction,data){if(data.choices){if(Element.isA(interaction,"matchInteraction"))for(let set=0;2>set;set++){if(!data.choices[set])throw new Error(`missing match set #${set}`);const matchSet=data.choices[set];for(let serial in matchSet){const choice=this.buildElement(matchSet[serial]);choice&&interaction.addChoice(choice,set)}}else for(let serial in data.choices){const choice=this.buildElement(data.choices[serial]);choice&&interaction.addChoice(choice)}if(Element.isA(interaction,"graphicGapMatchInteraction")&&data.gapImgs)for(let serial in data.gapImgs){const gapImg=this.buildElement(data.gapImgs[serial]);gapImg&&interaction.addGapImg(gapImg)}}},loadChoiceData(choice,data){if(Element.isA(choice,"textVariableChoice"))choice.val(data.text);else if(Element.isA(choice,"gapImg"));else if(Element.isA(choice,"gapText"))choice.body()||choice.body(data.text);else if(Element.isA(choice,"containerChoice"));},loadObjectData(object,data){object.setAttributes(data.attributes),data._alt&&("object"===data._alt.qtiClass?object._alt=Loader.buildElement(data._alt):object._alt=data._alt)},loadMathData(math,data){math.ns=data.ns||{},math.setMathML(data.mathML||""),_.forIn(data.annotations||{},(value,encoding)=>{math.setAnnotation(encoding,value)})},loadTooltipData(tooltip,data){tooltip.content(data.content)},loadPciData(pci,data){loadPortableCustomElementData(pci,data)},loadPicData(pic,data){loadPortableCustomElementData(pic,data)}});return Loader}),define("taoQtiItem/qtiCreator/model/helper/event",["jquery","lodash"],function($,_){"use strict";var namespace=".qti-creator",namespaceModel=".qti-creator",eventList=["containerBodyChange","containerElementAdded","elementCreated.qti-widget","attributeChange.qti-widget","choiceCreated.qti-widget","correctResponseChange.qti-widget","mapEntryChange.qti-widget","mapEntryRemove.qti-widget","deleted.qti-widget","choiceTextChange.qti-widget","responseTemplateChange.qti-widget","mappingAttributeChange.qti-widget","feedbackRuleConditionChange.qti-widget","feedbackRuleCreated.qti-widget","feedbackRuleRemoved.qti-widget","feedbackRuleElseCreated.qti-widget","feedbackRuleElseRemoved.qti-widget"],event={choiceCreated:function choiceCreated(choice,interaction){$(document).trigger("choiceCreated.qti-widget",{choice:choice,interaction:interaction})},choiceDeleted:function choiceDeleted(choice,interaction){$(document).trigger("choiceDeleted.qti-widget",{choice:choice,interaction:interaction})},deleted:function(element,parent){element.isset()&&element.unset(),$(document).off("."+element.getSerial()),$(document).trigger("deleted.qti-widget",{element:element,parent:parent})},getList:function(addedNamespace){var events=_.clone(eventList);return addedNamespace?_.map(events,function(e){return e+"."+addedNamespace}):events},initElementToWidgetListeners:function(){var ns=".widget-container";$(document).off(".widget-container").on(event.getList(".widget-container").join(" "),function(e,data){var element=data.element||data.container||null,widget=data&&element&&element.data("widget");!widget&&data.parent&&(element=data.parent,widget=element.data("widget")),widget&&widget.$container.trigger(e.type+".qti-creator"+namespaceModel,data)})},getNs:function(){return".qti-creator"},getNsModel:function(){return namespaceModel}};return event}),define("taoQtiItem/qtiCreator/model/helper/invalidator",["lodash"],function(_){"use strict";const invalidator={completelyValid:function(element){const item=element.getRootElement();if(item){const serial=element.getSerial(),invalidElements=item.data("invalid")||{};delete invalidElements[serial],item.data("invalid",invalidElements)}},valid:function(element,key){const item=element.getRootElement(),serial=element.getSerial();if(item){const invalidElements=item.data("invalid")||{};if(key)invalidElements[serial]&&invalidElements[serial][key]&&(delete invalidElements[serial][key],!_.size(invalidElements[serial])&&delete invalidElements[serial],item.data("invalid",invalidElements));else throw new Error("missing required argument \"key\"")}},invalid:function(element,key,message,stateName){const item=element.getRootElement(),serial=element.getSerial();if(item){const invalidElements=item.data("invalid")||{};if(key)invalidElements[serial]||(invalidElements[serial]={}),invalidElements[serial][key]={message:message||"",stateName:stateName||"active"},item.data("invalid",invalidElements);else throw new Error("missing required arguments \"key\"")}},isValid:function(element,key){const item=element.getRootElement(),serial=element.getSerial();if(item){const invalidElements=item.data("invalid")||{};return key?!(invalidElements[serial]&&invalidElements[serial][key]):!invalidElements[serial]}return!0}};return invalidator}),define("taoQtiItem/qtiCreator/model/mixin/editable",["lodash","jquery","core/encoder/entity","taoQtiItem/qtiItem/core/Element","taoQtiItem/qtiCreator/model/helper/event","taoQtiItem/qtiCreator/model/helper/invalidator"],function(_,$,entity,Element,event,invalidator){"use strict";const _removeSelf=function(element){let removed=!1;const related=element.getRootElement();if(related){const found=related.find(element.getSerial());if(found){const parent=found.parent;if(element.getResponseDeclaration){const response=element.getResponseDeclaration();response&&(invalidator.completelyValid(response),Element.unsetElement(response))}Element.isA(parent,"interaction")?("gapImg"===element.qtiClass?parent.removeGapImg(element):Element.isA(element,"choice")&&parent.removeChoice(element),removed=!0):"body"===found.location&&_.isFunction(parent.initContainer)?(_.isFunction(element.beforeRemove)&&element.beforeRemove(),parent.getBody().removeElement(element),removed=!0):Element.isA(parent,"_container")&&(_.isFunction(element.beforeRemove)&&element.beforeRemove(),parent.removeElement(element),removed=!0),removed&&(element.data("removed",!0),invalidator.completelyValid(element),event.deleted(element,parent))}}else throw new Error("no related item found");return removed},_removeElement=function(element,containerPropName,eltToBeRemoved){let targetSerial="",targetElt;return element[containerPropName]&&("string"==typeof eltToBeRemoved?(targetSerial=eltToBeRemoved,targetElt=Element.getElementBySerial(targetSerial)):eltToBeRemoved instanceof Element&&(targetSerial=eltToBeRemoved.getSerial(),targetElt=eltToBeRemoved),targetSerial&&(invalidator.completelyValid(targetElt),delete element[containerPropName][targetSerial],Element.unsetElement(targetSerial))),element},methods={init:function(serial,attributes){const attr={};"object"==typeof serial&&(attributes=serial,serial=""),_.isFunction(this.getDefaultAttributes)&&_.extend(attr,this.getDefaultAttributes()),_.extend(attr,attributes),this._super(serial,attr)},attr:function(key,value){const ret=this._super(key,value);return"undefined"!=typeof key&&"undefined"!=typeof value&&$(document).trigger("attributeChange.qti-widget",{element:this,key:key,value:entity.encode(value)}),_.isString(ret)?entity.decode(ret):ret},removeAttr:function(key){const ret=this._super(key);return $(document).trigger("attributeChange.qti-widget",{element:this,key:key,value:null}),ret},remove:function(){if(0===arguments.length)return _removeSelf(this);if(2===arguments.length)return _removeElement(this,arguments[0],arguments[1]);throw new Error("invalid number of argument given")}};return methods}),define("taoQtiItem/qtiCreator/model/qtiClasses",["lodash","taoQtiItem/qtiItem/core/qtiClasses"],function(_,qtiClasses){"use strict";return _.defaults({assessmentItem:"taoQtiItem/qtiCreator/model/Item",_container:"taoQtiItem/qtiCreator/model/Container",figure:"taoQtiItem/qtiCreator/model/Figure",img:"taoQtiItem/qtiCreator/model/Img",figcaption:"taoQtiItem/qtiCreator/model/Figcaption",math:"taoQtiItem/qtiCreator/model/Math",object:"taoQtiItem/qtiCreator/model/Object",table:"taoQtiItem/qtiCreator/model/Table",rubricBlock:"taoQtiItem/qtiCreator/model/RubricBlock",modalFeedback:"taoQtiItem/qtiCreator/model/feedbacks/ModalFeedback",choiceInteraction:"taoQtiItem/qtiCreator/model/interactions/ChoiceInteraction",orderInteraction:"taoQtiItem/qtiCreator/model/interactions/OrderInteraction",associateInteraction:"taoQtiItem/qtiCreator/model/interactions/AssociateInteraction",matchInteraction:"taoQtiItem/qtiCreator/model/interactions/MatchInteraction",inlineChoiceInteraction:"taoQtiItem/qtiCreator/model/interactions/InlineChoiceInteraction",simpleChoice:"taoQtiItem/qtiCreator/model/choices/SimpleChoice",simpleAssociableChoice:"taoQtiItem/qtiCreator/model/choices/SimpleAssociableChoice",inlineChoice:"taoQtiItem/qtiCreator/model/choices/InlineChoice",mediaInteraction:"taoQtiItem/qtiCreator/model/interactions/MediaInteraction",uploadInteraction:"taoQtiItem/qtiCreator/model/interactions/UploadInteraction",textEntryInteraction:"taoQtiItem/qtiCreator/model/interactions/TextEntryInteraction",sliderInteraction:"taoQtiItem/qtiCreator/model/interactions/SliderInteraction",extendedTextInteraction:"taoQtiItem/qtiCreator/model/interactions/ExtendedTextInteraction",hotspotInteraction:"taoQtiItem/qtiCreator/model/interactions/HotspotInteraction",selectPointInteraction:"taoQtiItem/qtiCreator/model/interactions/SelectPointInteraction",graphicInteraction:"taoQtiItem/qtiCreator/model/interactions/GraphicOrderInteraction",graphicAssociateInteraction:"taoQtiItem/qtiCreator/model/interactions/GraphicAssociateInteraction",graphicGapMatchInteraction:"taoQtiItem/qtiCreator/model/interactions/GraphicGapMatchInteraction",graphicOrderInteraction:"taoQtiItem/qtiCreator/model/interactions/GraphicOrderInteraction",hotspotChoice:"taoQtiItem/qtiCreator/model/choices/HotspotChoice",gapImg:"taoQtiItem/qtiCreator/model/choices/GapImg",associableHotspot:"taoQtiItem/qtiCreator/model/choices/AssociableHotspot",gapMatchInteraction:"taoQtiItem/qtiCreator/model/interactions/GapMatchInteraction",hottextInteraction:"taoQtiItem/qtiCreator/model/interactions/HottextInteraction",hottext:"taoQtiItem/qtiCreator/model/choices/Hottext",gapText:"taoQtiItem/qtiCreator/model/choices/GapText",gap:"taoQtiItem/qtiCreator/model/choices/Gap",responseDeclaration:"taoQtiItem/qtiCreator/model/variables/ResponseDeclaration",responseProcessing:"taoQtiItem/qtiCreator/model/ResponseProcessing",customInteraction:"taoQtiItem/qtiCreator/model/interactions/PortableCustomInteraction",endAttemptInteraction:"taoQtiItem/qtiCreator/model/interactions/EndAttemptInteraction",infoControl:"taoQtiItem/qtiCreator/model/PortableInfoControl",include:"taoQtiItem/qtiCreator/model/Include",printedVariable:"taoQtiItem/qtiCreator/model/PrintedVariable",_tooltip:"taoQtiItem/qtiCreator/model/Tooltip"},qtiClasses)}),define("taoQtiItem/qtiCreator/model/helper/container",["jquery","lodash","taoQtiItem/qtiCreator/model/qtiClasses"],function($,_,qtiClasses){"use strict";var methods={createElements:function(container,body,callback){var regex=/{{([a-z_]+)\.?([a-z_]*):new}}/ig,required={};body.replace(regex,function(original,qtiClass){if(qtiClasses[qtiClass])required[qtiClass]=qtiClasses[qtiClass];else throw new Error("missing required class : "+qtiClass)}),require(_.values(required),function(){var Qti=_.reduce([].slice.call(arguments),function(acc,qtiClassElt){return acc[qtiClassElt.prototype.qtiClass]=qtiClassElt,acc},{}),promises=[],$doc=$(document),newElts={},newBody=body.replace(regex,function(original,qtiClass,subClass){var elt=new Qti[qtiClass];return Qti[qtiClass]?(container.getRenderer()&&elt.setRenderer(container.getRenderer()),newElts[elt.getSerial()]=elt,subClass&&(elt.typeIdentifier=subClass),elt.placeholder()):original});container.setElements(newElts,newBody),_.forEach(newElts,function(elt){_.isFunction(elt.buildIdentifier)&&elt.buildIdentifier(),_.isFunction(elt.afterCreate)&&promises.push(elt.afterCreate())}),"function"==typeof callback&&Promise.all(promises).then(function(){_.forEach(newElts,function(elt){$doc.trigger("elementCreated.qti-widget",{parent:container.parent(),element:elt})}),callback.call(container,newElts)}).catch(function(err){container.getRenderer().getCreatorContext().trigger("error",err)})})}};return methods}),define("taoQtiItem/qtiCreator/model/mixin/editableContainer",["taoQtiItem/qtiCreator/model/helper/container"],function(containerHelper){"use strict";var methods={createElements:function(body,callback){var _this=this;containerHelper.createElements(_this.getBody(),body,function(newElts){callback.call(_this,newElts)})}};return methods}),define("taoQtiItem/qtiItem/core/IdentifiedElement",["taoQtiItem/qtiItem/core/Element","taoQtiItem/qtiItem/helper/util"],function(Element,util){"use strict";Element=Element&&Object.prototype.hasOwnProperty.call(Element,"default")?Element["default"]:Element,util=util&&Object.prototype.hasOwnProperty.call(util,"default")?util["default"]:util;var IdentifiedElement=Element.extend({buildIdentifier:function buildIdentifier(prefix,useSuffix){var item=this.getRootElement(),id=util.buildIdentifier(item,prefix||this.qtiClass,useSuffix);return id&&this.attr("identifier",id),this},id:function id(value){return value||this.attr("identifier")||this.buildIdentifier(this.qtiClass,!0),this.attr("identifier",value)}});return IdentifiedElement}),define("taoQtiItem/qtiItem/mixin/Mixin",[],function(){"use strict";var Mixin={augment:function(targetClass,methods,options){if("function"==typeof targetClass&&"object"==typeof methods)for(var methodName in methods)if(!Object.hasOwnProperty(targetClass.prototype,methodName))targetClass.prototype[methodName]=methods[methodName];else if(options&&options.append){var _parent=targetClass.prototype[methodName];targetClass.prototype[methodName]=function(){return methods[methodName].apply(this,arguments),_parent.apply(this,arguments)}}}};return Mixin}),define("taoQtiItem/qtiItem/core/Container",["jquery","lodash","taoQtiItem/qtiItem/core/Element","taoQtiItem/qtiItem/helper/rendererConfig"],function($,_,Element,rendererConfig){"use strict";$=$&&Object.prototype.hasOwnProperty.call($,"default")?$["default"]:$,_=_&&Object.prototype.hasOwnProperty.call(_,"default")?_["default"]:_,Element=Element&&Object.prototype.hasOwnProperty.call(Element,"default")?Element["default"]:Element,rendererConfig=rendererConfig&&Object.prototype.hasOwnProperty.call(rendererConfig,"default")?rendererConfig["default"]:rendererConfig;var Container=Element.extend({qtiClass:"_container",init:function(body){if(this._super(),body&&"string"!=typeof body)throw"the body of a container must be a string";this.bdy=body||"",this.elements={}},body:function(body){if("undefined"==typeof body)return this.bdy;if("string"==typeof body)this.bdy=body,$(document).trigger("containerBodyChange",{body:body,container:this,parent:this.parent()});else throw"body must be a string"},setElements:function(elements,body){var returnValue=!1;for(var i in elements){var elt=elements[i];if(elt instanceof Element)body=body||this.bdy,-1===body.indexOf(elt.placeholder())&&(body+=elt.placeholder()),elt.setRootElement(this.getRootElement()||null),this.elements[elt.getSerial()]=elt,$(document).trigger("containerElementAdded",{element:elt,container:this}),returnValue=!0;else throw returnValue=!1,"expected a qti element"}return this.body(body),returnValue},setElement:function(element,body){return this.setElements([element],body)},removeElement:function(element){var serial="";return"string"==typeof element?serial=element:element instanceof Element&&(serial=element.getSerial()),delete this.elements[serial],this.body(this.body().replace("{{"+serial+"}}","")),this},getElements:function(qtiClass){var elts={};if("string"==typeof qtiClass)for(var serial in this.elements)Element.isA(this.elements[serial],qtiClass)&&(elts[serial]=this.elements[serial]);else elts=_.clone(this.elements);return elts},getElement:function(serial){return this.elements[serial]?this.elements[serial]:null},getComposingElements:function(){var elements=this.getElements(),elts={};for(var serial in elements)elts[serial]=elements[serial],elts=_.extend(elts,elements[serial].getComposingElements());return elts},render:function(){var args=rendererConfig.getOptionsFromArguments(arguments),renderer=args.renderer||this.getRenderer(),elementsData=[],tpl=this.body();for(var serial in this.elements){var elt=this.elements[serial];if("function"==typeof elt.render)"_container"===elt.qtiClass?tpl=tpl.replace(elt.placeholder(),elt.render(renderer)):(tpl=tpl.replace(elt.placeholder(),serial).replace(new RegExp(`{${serial}`),`{${serial}`).replace(serial,"{{{"+serial+"}}}"),elementsData[serial]=elt.render(renderer));else throw"render() is not defined for the qti element: "+serial}if(renderer.isRenderer)return this._super({body:renderer.renderDirect(tpl,elementsData),contentModel:this.contentModel||"flow"},renderer,args.placeholder);throw"invalid qti renderer for qti container"},postRender:function(data,altClassName,renderer){renderer=renderer||this.getRenderer();var res=_(this.elements).filter(function(elt){return"function"==typeof elt.postRender}).map(function(elt){return elt.postRender(data,"",renderer)}).flatten(!0).value().concat(this._super(data,altClassName,renderer));return res},toArray:function(){var arr={serial:this.serial,body:this.bdy,elements:{}};for(var serial in this.elements)arr.elements[serial]=this.elements[serial].toArray();return arr},find:function(serial,parent){var found=null;return this.elements[serial]?found={parent:parent||this,element:this.elements[serial],location:"body"}:_.forEach(this.elements,function(elt){if(found=elt.find(serial),found)return!1}),found},isEmpty:function(){return!this.bdy}});return Container}),define("taoQtiItem/qtiItem/mixin/Container",["taoQtiItem/qtiItem/mixin/Mixin","taoQtiItem/qtiItem/core/Container"],function(Mixin,Container$1){"use strict";Mixin=Mixin&&Object.prototype.hasOwnProperty.call(Mixin,"default")?Mixin["default"]:Mixin,Container$1=Container$1&&Object.prototype.hasOwnProperty.call(Container$1,"default")?Container$1["default"]:Container$1;var methods={initContainer:function(body){this.bdy=new Container$1(body||""),this.bdy.setRootElement(this.getRootElement()||null),this.bdy.contentModel="blockStatic"},getBody:function(){return this.bdy},body:function(body){var ret=this.bdy.body(body);return body?this:ret},setElement:function(element,body){return this.bdy.setElement(element,body),this},removeElement:function(element){return this.bdy.removeElement(element)},getElements:function(qtiClass){return this.bdy.getElements(qtiClass)},getElement:function(serial){return this.bdy.getElement(serial)}},Container={augment:function(targetClass){Mixin.augment(targetClass,methods)},methods:methods};return Container}),define("taoQtiItem/qtiItem/mixin/ContainerItemBody",["taoQtiItem/qtiItem/mixin/Mixin","taoQtiItem/qtiItem/mixin/Container","lodash"],function(Mixin,Container,_){"use strict";Mixin=Mixin&&Object.prototype.hasOwnProperty.call(Mixin,"default")?Mixin["default"]:Mixin,Container=Container&&Object.prototype.hasOwnProperty.call(Container,"default")?Container["default"]:Container,_=_&&Object.prototype.hasOwnProperty.call(_,"default")?_["default"]:_;var methods={};_.extend(methods,Container.methods),_.extend(methods,{initContainer:function(body){Container.methods.initContainer.call(this,body),this.bdy.contentModel="itemBody"}});var ContainerItemBody={augment:function(targetClass){Mixin.augment(targetClass,methods)},methods:methods};return ContainerItemBody}),define("taoQtiItem/qtiItem/core/Item",["taoQtiItem/qtiItem/core/Element","taoQtiItem/qtiItem/core/IdentifiedElement","taoQtiItem/qtiItem/mixin/ContainerItemBody","lodash","jquery","taoQtiItem/qtiItem/helper/util"],function(Element,IdentifiedElement,Container,_,$,util){"use strict";Element=Element&&Object.prototype.hasOwnProperty.call(Element,"default")?Element["default"]:Element,IdentifiedElement=IdentifiedElement&&Object.prototype.hasOwnProperty.call(IdentifiedElement,"default")?IdentifiedElement["default"]:IdentifiedElement,Container=Container&&Object.prototype.hasOwnProperty.call(Container,"default")?Container["default"]:Container,_=_&&Object.prototype.hasOwnProperty.call(_,"default")?_["default"]:_,$=$&&Object.prototype.hasOwnProperty.call($,"default")?$["default"]:$,util=util&&Object.prototype.hasOwnProperty.call(util,"default")?util["default"]:util;var Item=IdentifiedElement.extend({qtiClass:"assessmentItem",init:function init(serial,attributes){this._super(serial,attributes),this.rootElement=this,this.stylesheets={},this.responses={},this.outcomes={},this.modalFeedbacks={},this.namespaces={},this.schemaLocations={},this.responseProcessing=null,this.apipAccessibility=null},getInteractions:function getInteractions(){var interactions=[],elts=this.getComposingElements();for(var serial in elts)Element.isA(elts[serial],"interaction")&&interactions.push(elts[serial]);return interactions},addResponseDeclaration:function addResponseDeclaration(response){if(Element.isA(response,"responseDeclaration"))response.setRootElement(this),this.responses[response.getSerial()]=response;else throw"is not a qti response declaration";return this},getResponseDeclaration:function getResponseDeclaration(identifier){for(var i in this.responses)if(this.responses[i].attr("identifier")===identifier)return this.responses[i];return null},addOutcomeDeclaration:function addOutcomeDeclaration(outcome){if(Element.isA(outcome,"outcomeDeclaration"))outcome.setRootElement(this),this.outcomes[outcome.getSerial()]=outcome;else throw"is not a qti outcome declaration";return this},getOutcomeDeclaration:function getOutcomeDeclaration(identifier){var found;return _.forEach(this.outcomes,function(outcome){if(outcome.id()===identifier)return found=outcome,!1}),found},getOutcomes:function getOutcomes(){return _.clone(this.outcomes)},removeOutcome:function removeOutcome(identifier){var outcome=this.getOutcomeDeclaration(identifier);outcome&&(this.outcomes=_.omit(this.outcomes,outcome.getSerial()))},addModalFeedback:function addModalFeedback(feedback){if(Element.isA(feedback,"modalFeedback"))feedback.setRootElement(this),this.modalFeedbacks[feedback.getSerial()]=feedback;else throw"is not a qti modal feedback";return this},getComposingElements:function getComposingElements(){var elts=this._super(),_this=this;return _.forEach(["responses","outcomes","modalFeedbacks","stylesheets"],function(elementCollection){for(var i in _this[elementCollection]){var elt=_this[elementCollection][i];elts[i]=elt,elts=_.extend(elts,elt.getComposingElements())}}),this.responseProcessing instanceof Element&&(elts[this.responseProcessing.getSerial()]=this.responseProcessing),elts},find:function find(serial){var found=this._super(serial);return found||(found=util.findInCollection(this,["responses","outcomes","modalFeedbacks","stylesheets"],serial)),found},getResponses:function getResponses(){return _.clone(this.responses)},getRootElement:function getRootElement(){return this},addNamespace:function addNamespace(name,uri){this.namespaces[name]=uri},setNamespaces:function setNamespaces(namespaces){this.namespaces=namespaces},getNamespaces:function getNamespaces(){return _.clone(this.namespaces)},setSchemaLocations:function setSchemaLocations(locations){this.schemaLocations=locations},getSchemaLocations:function getSchemaLocations(){return _.clone(this.schemaLocations)},setApipAccessibility:function setApipAccessibility(apip){this.apipAccessibility=apip||null},getApipAccessibility:function getApipAccessibility(){return this.apipAccessibility},addStylesheet:function addStylesheet(stylesheet){if(Element.isA(stylesheet,"stylesheet"))stylesheet.setRootElement(this),this.stylesheets[stylesheet.getSerial()]=stylesheet;else throw"is not a qti stylesheet declaration";return this},removeStyleSheet:function removeStyleSheet(stylesheet){return delete this.stylesheets[stylesheet.getSerial()],this},stylesheetExists:function stylesheetExists(href){var exists=!1;return _.forEach(this.stylesheets,function(stylesheet){if(stylesheet.attr("href")===href)return exists=!0,!1}),exists},setResponseProcessing:function setResponseProcessing(rp){if(Element.isA(rp,"responseProcessing"))rp.setRootElement(this),this.responseProcessing=rp;else throw"is not a response processing";return this},toArray:function toArray(){var arr=this._super(),toArray=function(elt){return elt.toArray()};return arr.namespaces=this.namespaces,arr.schemaLocations=this.schemaLocations,arr.outcomes=_.map(this.outcomes,toArray),arr.responses=_.map(this.responses,toArray),arr.stylesheets=_.map(this.stylesheets,toArray),arr.modalFeedbacks=_.map(this.modalFeedbacks,toArray),arr.responseProcessing=this.responseProcessing.toArray(),arr},isEmpty:function isEmpty(){var body=this.body().trim();if(body){var $dummy=$("
").html(body),$children=$dummy.children();return!!(1===$children.length&&$children.hasClass("empty"))}return!0},clear:function clear(){var renderer=this.getRenderer();renderer&&_.isFunction(renderer.destroy)&&renderer.destroy(this)}});return Container.augment(Item),Item}),define("taoQtiItem/qtiItem/core/Stylesheet",["taoQtiItem/qtiItem/core/Element","lodash","taoQtiItem/qtiItem/helper/rendererConfig"],function(Element,_,rendererConfig){"use strict";Element=Element&&Object.prototype.hasOwnProperty.call(Element,"default")?Element["default"]:Element,_=_&&Object.prototype.hasOwnProperty.call(_,"default")?_["default"]:_,rendererConfig=rendererConfig&&Object.prototype.hasOwnProperty.call(rendererConfig,"default")?rendererConfig["default"]:rendererConfig;var Stylesheet=Element.extend({qtiClass:"stylesheet",render:function(){var args=rendererConfig.getOptionsFromArguments(arguments),renderer=args.renderer||this.getRenderer(),defaultData={};return defaultData.attributes={href:renderer.resolveUrl(this.attr("href"))},this._super(_.merge(defaultData,args.data),args.placeholder,args.subclass,renderer)}});return Stylesheet}),define("taoQtiItem/qtiCreator/model/Stylesheet",["lodash","taoQtiItem/qtiCreator/model/mixin/editable","taoQtiItem/qtiItem/core/Stylesheet"],function(_,editable,Stylesheet){"use strict";var methods={};return _.extend(methods,editable),_.extend(methods,{getDefaultAttributes:function(){return{href:"css/tao-user-styles.css",title:"",type:"text/css",media:"all"}}}),Stylesheet.extend(methods)}),define("taoQtiItem/qtiItem/core/ResponseProcessing",["taoQtiItem/qtiItem/core/Element"],function(Element){"use strict";Element=Element&&Object.prototype.hasOwnProperty.call(Element,"default")?Element["default"]:Element;var ResponseProcessing=Element.extend({qtiClass:"responseProcessing",processingType:"",xml:"",toArray:function(){var arr=this._super();return arr.processingType=this.processingType,arr.xml=this.xml,arr}});return ResponseProcessing}),define("taoQtiItem/qtiItem/helper/interactionHelper",["lodash","taoQtiItem/qtiItem/core/Element"],function(_,Element){"use strict";var _Mathfloor=Math.floor;_=_&&Object.prototype.hasOwnProperty.call(_,"default")?_["default"]:_,Element=Element&&Object.prototype.hasOwnProperty.call(Element,"default")?Element["default"]:Element;var interactionHelper={convertChoices:function(choices,outputType){var ret=[],_this=this;return _.forEach(choices,function(c){Element.isA(c,"choice")?"serial"===outputType?ret.push(c.getSerial()):"identifier"===outputType?ret.push(c.id()):ret.push(c):_.isArray(c)&&ret.push(_this.convertChoices(c,outputType))}),ret},findChoices:function(interaction,choices,inputType){var ret=[],_this=this;return _.forEach(choices,function(c){var choice;_.isString(c)?"serial"===inputType?(choice=interaction.getChoice(c),choice&&ret.push(choice)):"identifier"===inputType?(choice=interaction.getChoiceByIdentifier(c),choice&&ret.push(choice)):ret.push(c):_.isArray(c)?ret.push(_this.findChoices(interaction,c,inputType)):ret.push(c)}),ret},shuffleChoices:function(choices){var r=[],f={},j=0;for(var i in choices)if(Element.isA(choices[i],"choice")){var choice=choices[i];choice.attr("fixed")&&(f[j]=choice),r.push(choice),j++}else throw"invalid element in array: is not a qti choice";for(var n=0;n!0).addList(required.map(module=>({module,category:"qti"}))).load().then(loaded=>{loaded.forEach(clazz=>{_isValidRenderer(clazz)&&(_renderers[clazz.qtiClass]=clazz)}),"function"==typeof callback&&callback.call(self,_renderers)}),this},this.unload=function unload(){return this.themeLoader&&themeLoader(options.themes).unload(),this},this.setShuffledChoices=function(interaction,choices,identificationType){Element.isA(interaction,"interaction")&&(this.shuffledChoices[interaction.getSerial()]=interactionHelper.findChoices(interaction,choices,identificationType).map(val=>val.serial))},this.getShuffledChoices=function(interaction,reshuffle,returnedType){let choices=[],serial,i;if(Element.isA(interaction,"interaction")){if(serial=interaction.getSerial(),this.shuffledChoices[serial]&&!reshuffle)Element.isA(interaction,"matchInteraction")?_.forEach(choices,function(choice,index){2>index&&_.forEach(this.shuffledChoices[serial][i],function(choiceSerial){choice.push(interaction.getChoice(choiceSerial))})}):_.forEach(this.shuffledChoices[serial],function(choiceSerial){choices.push(interaction.getChoice(choiceSerial))});else if(Element.isA(interaction,"matchInteraction"))for(this.shuffledChoices[serial]=[],i=0;2>i;i++)choices[i]=interactionHelper.shuffleChoices(interaction.getChoices(i)),this.shuffledChoices[serial][i]=choices[i].map(val=>val.serial);else choices=interactionHelper.shuffleChoices(interaction.getChoices()),this.shuffledChoices[serial]=choices.map(val=>val.serial);return"serial"===returnedType||"identifier"===returnedType?interactionHelper.convertChoices(choices,returnedType):_.clone(choices)}return[]},this.getRenderers=function(){return _renderers},this.getLocations=function(){return _locations},this.resolveUrl=function resolveUrl(url){return options.assetManager?"string"==typeof url&&0{const responseDeclaration=interaction.getResponseDeclaration(),template=responseHelper.getTemplateNameFromUri(responseDeclaration.template);return"NONE"!==template}),outcomesWithExternalScored=customOutcomes.filter(outcome=>externalScoredValues.includes(outcome.attr("externalScored")));!scoreOutcome.attr("externalScored")&&isAllResponseProcessingRulesNone&&0===outcomesWithExternalScored.size()&&(item.removeOutcome("MAXSCORE"),item.removeOutcome("SCORE"))}},getMatchMaxOrderedChoices(choiceCollection){return _(choiceCollection).map(function(choice){var matchMax=parseInt(choice.attr("matchMax"),10);return _.isNaN(matchMax)&&(matchMax=0),{matchMax:0===matchMax?1/0:matchMax,id:choice.id()}}).sortBy("matchMax").reverse().valueOf()},choiceInteractionBased(interaction,options){var responseDeclaration=interaction.getResponseDeclaration(),mapDefault=parseFloat(responseDeclaration.mappingAttributes.defaultValue||0),template=responseHelper.getTemplateNameFromUri(responseDeclaration.template),max,maxChoice,minChoice,scoreMaps,requiredChoiceCount,totalAnswerableResponse,sortedMapEntries,missingMapsCount;if(options=_.defaults(options||{},{maxChoices:0,minChoices:0}),maxChoice=parseInt(interaction.attr("maxChoices")||options.maxChoices,10),minChoice=0,maxChoice&&minChoice&&maxChoicemaxChoice||responseDeclaration.correctResponse.lengthscore&&0>=requiredChoiceCount&&(score=0),requiredChoiceCount--,gamp.add(acc,score)},0),responseDeclaration.mappingAttributes.upperBound&&(max=_Mathmin(max,parseFloat(responseDeclaration.mappingAttributes.upperBound||0)))}else"MAP_RESPONSE_POINT"===template?max=0:"NONE"===template&&(max=0);return max},orderInteractionBased(interaction){var minChoice=0,maxChoice=parseInt(interaction.attr("maxChoices")||0,10),responseDeclaration=interaction.getResponseDeclaration(),template=responseHelper.getTemplateNameFromUri(responseDeclaration.template),max;return maxChoice&&0&&0>maxChoice?0:("MATCH_CORRECT"===template?_.isArray(responseDeclaration.correctResponse)&&maxChoice&&responseDeclaration.correctResponse.length>maxChoice||0?max=0:responseDeclaration.correctResponse&&(!_.isArray(responseDeclaration.correctResponse)||responseDeclaration.correctResponse.length)?max=1:max=0:"MAP_RESPONSE"===template||"MAP_RESPONSE_POINT"===template?max=0:"NONE"===template&&(max=0),max)},associateInteractionBased(interaction,options){var responseDeclaration=interaction.getResponseDeclaration(),template=responseHelper.getTemplateNameFromUri(responseDeclaration.template),maxAssoc=parseInt(interaction.attr("maxAssociations")||0,10),minAssoc=0,mapDefault=parseFloat(responseDeclaration.mappingAttributes.defaultValue||0),max,requiredAssoc,totalAnswerableResponse,usedChoices,choicesIdentifiers,sortedMapEntries,i,allPossibleMapEntries,infiniteScoringPair;if(options=_.defaults(options||{},{possiblePairs:[],checkInfinitePair:!1}),maxAssoc&&minAssoc&&maxAssocmaxAssoc||minAssoc)?max=0:(max=1,choicesIdentifiers=[],_.forEach(responseDeclaration.correctResponse,function(pair){var choices;_.isString(pair)&&(choices=pair.trim().split(" "),_.isArray(choices)&&2===choices.length&&(choicesIdentifiers.push(choices[0].trim()),choicesIdentifiers.push(choices[1].trim())))}),_.forEach(_.countBy(choicesIdentifiers),function(count,identifier){var choice=interaction.getChoiceByIdentifier(identifier),matchMax;return choice?(matchMax=parseInt(choice.attr("matchMax"),10),matchMax&&matchMaxi;i++){if(choiceId=choices[i],!_usedChoices[choiceId]){if(choice=interaction.getChoiceByIdentifier(choiceId),!choice)return!1;_usedChoices[choiceId]={used:0,max:parseInt(choice.attr("matchMax"),10)}}if(_usedChoices[choiceId].max&&_usedChoices[choiceId].used===_usedChoices[choiceId].max)return!1;_usedChoices[choiceId].used++}return infiniteScoringPair=infiniteScoringPair||options.checkInfinitePair&&0v.score&&0>=requiredAssoc&&(score=0),requiredAssoc--,gamp.add(acc,score)},0),responseDeclaration.mappingAttributes.upperBound&&(max=_Mathmin(max,parseFloat(responseDeclaration.mappingAttributes.upperBound||0)))}else"MAP_RESPONSE_POINT"===template?max=0:"NONE"===template&&(max=0);return max},gapMatchInteractionBased(interaction){var responseDeclaration=interaction.getResponseDeclaration(),template=responseHelper.getTemplateNameFromUri(responseDeclaration.template),mapDefault=parseFloat(responseDeclaration.mappingAttributes.defaultValue||0),getMatchMaxOrderedChoices=function getMatchMaxOrderedChoices(choiceCollection){return _(choiceCollection).map(function(choice){return{matchMax:0===choice.attr("matchMax")?1/0:choice.attr("matchMax")||0,id:choice.id()}}).sortBy("matchMax").reverse().valueOf()},calculatePossiblePairs=function calculatePossiblePairs(gapMatchInteraction){var pairs=[],matchSet1=getMatchMaxOrderedChoices(gapMatchInteraction.getChoices()),matchSet2=getMatchMaxOrderedChoices(gapMatchInteraction.getGaps());return _.forEach(matchSet1,function(choice1){_.forEach(matchSet2,function(choice2){pairs.push([choice1.id,choice2.id])})}),pairs},max,skippableWrongResponse,totalAnswerableResponse,usedChoices,usedGaps,group1,group2,allPossibleMapEntries;if("MATCH_CORRECT"===template)responseDeclaration.correctResponse&&(!_.isArray(responseDeclaration.correctResponse)||responseDeclaration.correctResponse.length)?(max=1,group1=[],group2=[],_.forEach(responseDeclaration.correctResponse,function(pair){var choices;_.isString(pair)&&(choices=pair.trim().split(" "),_.isArray(choices)&&2===choices.length&&(group1.push(choices[0].trim()),group2.push(choices[1].trim())))}),_.forEach(_.countBy(group1),function(count,identifier){var choice=interaction.getChoiceByIdentifier(identifier),matchMax=parseInt(choice.attr("matchMax"),10);if(matchMax&&matchMaxlanguages.reduce((memo,lang)=>(memo[lang.code]=lang.label,memo),{}),useCKEFormatting=languages=>languages.map(lang=>`${lang.code}:${lang.label}:${lang.orientation}`),getList=()=>null===languagesRequest?languagesRequest=request(languagesUrl,null,null,headers):languagesRequest,isRTLbyLanguageCode=code=>getList().then(languages=>{const lang=languages.filter(lang=>lang.code===code);return lang[0]&&"rtl"===lang[0].orientation||!1});return{useLegacyFormatting,useCKEFormatting,getList,isRTLbyLanguageCode}}),define("taoQtiItem/qtiCreator/helper/itemLoader",["jquery","lodash","taoQtiItem/qtiItem/core/Loader","taoQtiItem/qtiCreator/model/Item","taoQtiItem/qtiCreator/model/qtiClasses","taoQtiItem/qtiItem/helper/itemScore","core/dataProvider/request","taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier","taoQtiItem/qtiCreator/helper/languages"],function($,_,Loader,Item,qtiClasses,itemScoreHelper,request,qtiIdentifier,languages){"use strict";const _generateIdentifier=function _generateIdentifier(uri){const pos=uri.lastIndexOf("#");return uri.substring(pos+1,pos+1+qtiIdentifier.maxQtiIdLength)},decodeHtml=function(str){const map={"&":"&","<":"<",">":">",""":"\"","'":"'"};return str.replace(/&|<|>|"|'/g,function(m){return map[m]})},qtiNamespace="http://www.imsglobal.org/xsd/imsqti_v2p2",qtiSchemaLocation={"http://www.imsglobal.org/xsd/imsqti_v2p2":"http://www.imsglobal.org/xsd/qti/qtiv2p2/imsqti_v2p2.xsd"},creatorLoader={loadItem:function loadItem(config,callback){if(config.uri){const langList=languages.getList(),itemRdf=request(config.itemDataUrl,{uri:config.uri}).catch(d=>d);Promise.all([langList,itemRdf]).then(_ref3=>{let[languagesList,data]=_ref3;if(data.itemData)for(const response in data.itemData.responses){const newObject={};for(const mapKey in data.itemData.responses[response].mapping)newObject[decodeHtml(mapKey)]=data.itemData.responses[response].mapping[mapKey];data.itemData.responses[response].mapping=newObject}if(data.itemData&&"assessmentItem"===data.itemData.qtiClass){const loader=new Loader().setClassesLocation(qtiClasses),itemData=data.itemData;loader.loadItemData(itemData,function(loadedItem){loadedItem.isEmpty()&&loadedItem.body("");const namespaces=loadedItem.getNamespaces();namespaces[""]=qtiNamespace,loadedItem.setNamespaces(namespaces),loadedItem.setSchemaLocations(qtiSchemaLocation),languagesList&&loadedItem.data("languagesList",languagesList);const{responseProcessing:{processingType}={}}=loadedItem;if(!config.perInteractionRp&&"templateDriven"===processingType){const{responses={},responseProcessing:{data,responseRules=[]}={}}=itemData,responseIdentifiers=[];_.forOwn(responses,_ref4=>{let{identifier}=_ref4;responseIdentifiers.push(identifier)});const itemScoreRP=itemScoreHelper(responseIdentifiers);responseRules.some(responseRule=>_.isEqual(responseRule,itemScoreRP))&&loadedItem.responseProcessing.setProcessingType("custom",data)}callback(loadedItem,this.getLoadedClasses())})}else{const newItem=new Item().id(_generateIdentifier(config.uri)).attr("title",config.label);newItem.createResponseProcessing(),newItem.setNamespaces({"":qtiNamespace,xsi:"http://www.w3.org/2001/XMLSchema-instance",m:"http://www.w3.org/1998/Math/MathML"}),newItem.setSchemaLocations(qtiSchemaLocation),newItem.data("new",!0),languagesList&&newItem.data("languagesList",languagesList),callback(newItem)}})}}};return creatorLoader}),define("taoQtiItem/portableElementRegistry/assetManager/portableAssetStrategy",["taoQtiItem/portableElementRegistry/ciRegistry","taoQtiItem/portableElementRegistry/icRegistry"],function(ciRegistry,icRegistry){"use strict";function getBaseUrlByIdentifier(typeIdentifier){return ciRegistry.get(typeIdentifier)?ciRegistry.getBaseUrl(typeIdentifier):icRegistry.get(typeIdentifier)?icRegistry.getBaseUrl(typeIdentifier):typeIdentifier}return{name:"portableElementLocation",handle:function handlePortableElementLocation(url){if(url.file===url.path)return getBaseUrlByIdentifier(url.file);return url.source===url.relative?url.relative.replace(/^(\.\/)?([a-z_0-9]+)\/(.*)/i,function(fullmatch,$1,typeIdentifier,relPath){var runtimeLocation=getBaseUrlByIdentifier(typeIdentifier);return runtimeLocation?runtimeLocation+"/"+relPath:fullmatch}):void 0}}}),define("taoQtiItem/qtiCommonRenderer/renderers/config",["context","ui/themes","taoItems/assets/manager","taoItems/assets/strategies","module","taoQtiItem/portableElementRegistry/assetManager/portableAssetStrategy"],function(context,themes,assetManagerFactory,assetStrategies,module,portableAssetStrategy){"use strict";context=context&&Object.prototype.hasOwnProperty.call(context,"default")?context["default"]:context,themes=themes&&Object.prototype.hasOwnProperty.call(themes,"default")?themes["default"]:themes,assetManagerFactory=assetManagerFactory&&Object.prototype.hasOwnProperty.call(assetManagerFactory,"default")?assetManagerFactory["default"]:assetManagerFactory,assetStrategies=assetStrategies&&Object.prototype.hasOwnProperty.call(assetStrategies,"default")?assetStrategies["default"]:assetStrategies,module=module&&Object.prototype.hasOwnProperty.call(module,"default")?module["default"]:module,portableAssetStrategy=portableAssetStrategy&&Object.prototype.hasOwnProperty.call(portableAssetStrategy,"default")?portableAssetStrategy["default"]:portableAssetStrategy;var itemThemes=themes.get("items"),moduleConfig=module.config(),assetManager=assetManagerFactory([{name:"theme",handle:function handleTheme(url){if(itemThemes&&url.path&&(url.path===itemThemes.base||itemThemes.available&&itemThemes.available.map(val=>val.path).includes(url.path)))return context.root_url+url.toString()}},assetStrategies.taomedia,assetStrategies.external,assetStrategies.base64,assetStrategies.itemCssNoCache,assetStrategies.baseUrl,portableAssetStrategy],{baseUrl:""}),locations={assessmentItem:"taoQtiItem/qtiCommonRenderer/renderers/Item",_container:"taoQtiItem/qtiCommonRenderer/renderers/Container",_simpleFeedbackRule:!1,_tooltip:"taoQtiItem/qtiCommonRenderer/renderers/Tooltip",stylesheet:"taoQtiItem/qtiCommonRenderer/renderers/Stylesheet",outcomeDeclaration:!1,responseDeclaration:!1,responseProcessing:!1,figure:"taoQtiItem/qtiCommonRenderer/renderers/Figure",img:"taoQtiItem/qtiCommonRenderer/renderers/Img",figcaption:"taoQtiItem/qtiCommonRenderer/renderers/Figcaption",math:"taoQtiItem/qtiCommonRenderer/renderers/Math",object:"taoQtiItem/qtiCommonRenderer/renderers/Object",table:"taoQtiItem/qtiCommonRenderer/renderers/Table",printedVariable:"taoQtiItem/qtiCommonRenderer/renderers/PrintedVariable",rubricBlock:"taoQtiItem/qtiCommonRenderer/renderers/RubricBlock",modalFeedback:"taoQtiItem/qtiCommonRenderer/renderers/ModalFeedback",prompt:"taoQtiItem/qtiCommonRenderer/renderers/interactions/Prompt",choiceInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/ChoiceInteraction",extendedTextInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/ExtendedTextInteraction",orderInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/OrderInteraction",associateInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/AssociateInteraction",matchInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/MatchInteraction",textEntryInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/TextEntryInteraction",sliderInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/SliderInteraction",inlineChoiceInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/InlineChoiceInteraction","simpleChoice.choiceInteraction":"taoQtiItem/qtiCommonRenderer/renderers/choices/SimpleChoice.ChoiceInteraction","simpleChoice.orderInteraction":"taoQtiItem/qtiCommonRenderer/renderers/choices/SimpleChoice.OrderInteraction",hottext:"taoQtiItem/qtiCommonRenderer/renderers/choices/Hottext",gap:"taoQtiItem/qtiCommonRenderer/renderers/choices/Gap",gapText:"taoQtiItem/qtiCommonRenderer/renderers/choices/GapText","simpleAssociableChoice.matchInteraction":"taoQtiItem/qtiCommonRenderer/renderers/choices/SimpleAssociableChoice.MatchInteraction","simpleAssociableChoice.associateInteraction":"taoQtiItem/qtiCommonRenderer/renderers/choices/SimpleAssociableChoice.AssociateInteraction",inlineChoice:"taoQtiItem/qtiCommonRenderer/renderers/choices/InlineChoice",hottextInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/HottextInteraction",hotspotInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/HotspotInteraction",hotspotChoice:!1,gapMatchInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/GapMatchInteraction",selectPointInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/SelectPointInteraction",graphicOrderInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/GraphicOrderInteraction",mediaInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/MediaInteraction",uploadInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/UploadInteraction",graphicGapMatchInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/GraphicGapMatchInteraction",gapImg:"taoQtiItem/qtiCommonRenderer/renderers/choices/GapImg",graphicAssociateInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/GraphicAssociateInteraction",associableHotspot:!1,customInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/PortableCustomInteraction",infoControl:"taoQtiItem/qtiCommonRenderer/renderers/PortableInfoControl",include:"taoQtiItem/qtiCommonRenderer/renderers/Include",endAttemptInteraction:"taoQtiItem/qtiCommonRenderer/renderers/interactions/EndAttemptInteraction"},config={name:"commonRenderer",locations:locations,options:{assetManager:assetManager,themes:itemThemes,enableDragAndDrop:{associate:!!moduleConfig.associateDragAndDrop,gapMatch:!!moduleConfig.gapMatchDragAndDrop,graphicGapMatch:!!moduleConfig.graphicGapMatchDragAndDrop,order:!!moduleConfig.orderDragAndDrop},messages:moduleConfig.messages}};return config}),define("taoQtiItem/qtiCreator/renderers/config",["lodash","context","taoQtiItem/qtiCommonRenderer/renderers/config","taoItems/assets/manager","taoItems/assets/strategies"],function(_,context,commonRenderConfig,assetManagerFactory,assetStrategies){"use strict";var assetManager=assetManagerFactory([assetStrategies.taomedia,assetStrategies.external,assetStrategies.base64,assetStrategies.baseUrl],{baseUrl:""}),locations=_.defaults({_container:"taoQtiItem/qtiCreator/renderers/Container",_tooltip:"taoQtiItem/qtiCreator/renderers/Tooltip",assessmentItem:"taoQtiItem/qtiCreator/renderers/Item",rubricBlock:"taoQtiItem/qtiCreator/renderers/RubricBlock",figure:"taoQtiItem/qtiCreator/renderers/Figure",img:"taoQtiItem/qtiCreator/renderers/Img",figcaption:"taoQtiItem/qtiCreator/renderers/Figcaption",math:"taoQtiItem/qtiCreator/renderers/Math",object:"taoQtiItem/qtiCreator/renderers/Object",table:"taoQtiItem/qtiCreator/renderers/Table",modalFeedback:"taoQtiItem/qtiCreator/renderers/ModalFeedback",choiceInteraction:"taoQtiItem/qtiCreator/renderers/interactions/ChoiceInteraction",orderInteraction:"taoQtiItem/qtiCreator/renderers/interactions/OrderInteraction",matchInteraction:"taoQtiItem/qtiCreator/renderers/interactions/MatchInteraction",associateInteraction:"taoQtiItem/qtiCreator/renderers/interactions/AssociateInteraction",inlineChoiceInteraction:"taoQtiItem/qtiCreator/renderers/interactions/InlineChoiceInteraction",textEntryInteraction:"taoQtiItem/qtiCreator/renderers/interactions/TextEntryInteraction",hotspotInteraction:"taoQtiItem/qtiCreator/renderers/interactions/HotspotInteraction",selectPointInteraction:"taoQtiItem/qtiCreator/renderers/interactions/SelectPointInteraction",graphicOrderInteraction:"taoQtiItem/qtiCreator/renderers/interactions/GraphicOrderInteraction",graphicAssociateInteraction:"taoQtiItem/qtiCreator/renderers/interactions/GraphicAssociateInteraction",graphicGapMatchInteraction:"taoQtiItem/qtiCreator/renderers/interactions/GraphicGapMatchInteraction",mediaInteraction:"taoQtiItem/qtiCreator/renderers/interactions/MediaInteraction",uploadInteraction:"taoQtiItem/qtiCreator/renderers/interactions/UploadInteraction",sliderInteraction:"taoQtiItem/qtiCreator/renderers/interactions/SliderInteraction",extendedTextInteraction:"taoQtiItem/qtiCreator/renderers/interactions/ExtendedTextInteraction",inlineChoice:"taoQtiItem/qtiCreator/renderers/choices/InlineChoice","simpleChoice.choiceInteraction":"taoQtiItem/qtiCreator/renderers/choices/SimpleChoice.ChoiceInteraction","simpleChoice.orderInteraction":"taoQtiItem/qtiCreator/renderers/choices/SimpleChoice.OrderInteraction","simpleAssociableChoice.associateInteraction":"taoQtiItem/qtiCreator/renderers/choices/SimpleAssociableChoice.AssociateInteraction","simpleAssociableChoice.matchInteraction":"taoQtiItem/qtiCreator/renderers/choices/SimpleAssociableChoice.MatchInteraction",gapMatchInteraction:"taoQtiItem/qtiCreator/renderers/interactions/GapMatchInteraction",hottextInteraction:"taoQtiItem/qtiCreator/renderers/interactions/HottextInteraction",customInteraction:"taoQtiItem/qtiCreator/renderers/interactions/PortableCustomInteraction",endAttemptInteraction:"taoQtiItem/qtiCreator/renderers/interactions/EndAttemptInteraction",infoControl:"taoQtiItem/qtiCreator/renderers/PortableInfoControl",include:"taoQtiItem/qtiCreator/renderers/Include",gap:"taoQtiItem/qtiCreator/renderers/choices/Gap",gapText:"taoQtiItem/qtiCreator/renderers/choices/GapText",hottext:"taoQtiItem/qtiCreator/renderers/choices/Hottext",printedVariable:"taoQtiItem/qtiCreator/renderers/PrintedVariable"},commonRenderConfig.locations);return{name:"creatorRenderer",locations:locations,options:{assetManager:assetManager}}}),define("taoQtiItem/qtiCreator/renderers/Renderer",["taoQtiItem/qtiRunner/core/Renderer","taoQtiItem/qtiCreator/renderers/config"],function(Renderer,config){"use strict";return Renderer.build(config.locations,config.name,config.options)}),define("taoQtiItem/qtiCreator/helper/creatorRenderer",["jquery","lodash","taoQtiItem/qtiCreator/renderers/Renderer","taoItems/assets/manager","taoItems/assets/strategies","util/dom"],function($,_,Renderer,assetManagerFactory,assetStrategies,dom){"use strict";var _creatorRenderer=null,_configurableInteractions=["endAttempt"],_extractInteractionsConfig=function _extractInteractionsConfig(config){var ret={};return config&&config.properties&&_.forEach(_configurableInteractions,function(interactionName){config.properties[interactionName]&&(ret[interactionName]=config.properties[interactionName])}),ret},get=function(reset,config,areaBroker){var $bodyEltForm;return config=config||{},config.properties=config.properties||{},(!_creatorRenderer||reset)&&($bodyEltForm=_creatorRenderer?_creatorRenderer.getOption("bodyElementOptionForm"):null,(reset||!$bodyEltForm||!$bodyEltForm.length||!dom.contains($bodyEltForm))&&(_creatorRenderer=new Renderer({lang:"",uri:"",shuffleChoices:!1,itemOptionForm:$("#item-editor-item-property-bar .panel"),interactionOptionForm:$("#item-editor-interaction-property-bar .panel"),choiceOptionForm:$("#item-editor-choice-property-bar .panel"),responseOptionForm:$("#item-editor-response-property-bar .panel"),bodyElementOptionForm:areaBroker.getElementPropertyPanelArea(),textOptionForm:$("#item-editor-text-property-bar .panel"),modalFeedbackOptionForm:$("#item-editor-modal-feedback-property-bar .panel"),mediaManager:{appendContainer:"#mediaManager",browseUrl:config.properties.getFilesUrl,uploadUrl:config.properties.fileUploadUrl,deleteUrl:config.properties.fileDeleteUrl,downloadUrl:config.properties.fileDownloadUrl,fileExistsUrl:config.properties.fileExistsUrl,mediaSourcesUrl:config.properties.mediaSourcesUrl},interactions:_extractInteractionsConfig(config),qtiCreatorContext:config.qtiCreatorContext}),_creatorRenderer.getAssetManager().setData({baseUrl:config.properties.baseUrl||""}),_creatorRenderer.setAreaBroker(areaBroker),_.assign(_creatorRenderer,{getCreatorContext:function getCreatorContext(){return this.getOption("qtiCreatorContext")}}))),_creatorRenderer};return{get:get,setOption:function(name,value){return get().setOption(name,value)},setOptions:function(options){return get().setOptions(options)},load:function(qtiClasses,done){return get().load(function(){_.isFunction(done)&&done.apply(this,arguments)},qtiClasses)}}}),define("taoQtiItem/qtiCommonRenderer/renderers/Renderer",["taoQtiItem/qtiRunner/core/Renderer","taoQtiItem/qtiCommonRenderer/renderers/config"],function(Renderer$1,config){"use strict";Renderer$1=Renderer$1&&Object.prototype.hasOwnProperty.call(Renderer$1,"default")?Renderer$1["default"]:Renderer$1,config=config&&Object.prototype.hasOwnProperty.call(config,"default")?config["default"]:config;var Renderer=Renderer$1.build(config.locations,config.name,config.options);return Renderer}),define("taoQtiItem/qtiCommonRenderer/helpers/container",["lodash","jquery","taoQtiItem/qtiItem/core/Element"],function(_,$,Element){"use strict";_=_&&Object.prototype.hasOwnProperty.call(_,"default")?_["default"]:_,$=$&&Object.prototype.hasOwnProperty.call($,"default")?$["default"]:$,Element=Element&&Object.prototype.hasOwnProperty.call(Element,"default")?Element["default"]:Element;let _containers={},_$containerContext=$();const _getSelector=function(element){const serial=element.getSerial();let selector=`[data-serial=${serial}]`;return Element.isA(element,"choice")?selector=`.qti-choice${selector}`:Element.isA(element,"interaction")&&(selector=`.qti-interaction${selector}`),selector},containerHelper={setContext($scope){_$containerContext=$scope},getContext(){return _$containerContext},get(element,$scope){const serial=element.getSerial();return $scope instanceof $&&$scope.length?$scope.find(_getSelector(element)):_$containerContext instanceof $&&_$containerContext.length?_$containerContext.find(_getSelector(element)):(_containers[serial]&&_containers[serial].length||(_containers[serial]=$(_getSelector(element))),_containers[serial])},reset(element){element instanceof Element&&_containers[element.getSerial()]&&(_containers=_.omit(_containers,element.getSerial()))},clear(){_containers={},_$containerContext=$()},trigger(eventType,element,data){eventType&&(data&&!_.isArray(data)&&(data=[data]),this.get(element).trigger(eventType,data))},triggerResponseChangeEvent(interaction,extraData){this.trigger("responseChange",interaction,[{interaction:interaction,response:interaction.getResponse()},extraData])},targetBlank($container){$container.on("click","a",function(e){e.preventDefault();const href=$(this).attr("href");href&&href.match(/^http/i)&&window.open(href,"_blank")})}};return containerHelper}),define("taoQtiItem/qtiCreator/helper/commonRenderer",["lodash","taoQtiItem/qtiCommonRenderer/renderers/Renderer","taoQtiItem/qtiCommonRenderer/helpers/container"],function(_,Renderer,containerHelper){"use strict";var _$previousContext=null,commonRenderer={render:function(item,$container){return commonRenderer.setContext($container),_renderer.load(function(){$container.append(item.render(this)),item.postRender({},"",this)},item.getUsedClasses())},get:function(reset,config){return(!_renderer||reset)&&(_renderer=new Renderer({shuffleChoices:!0}),config&&_renderer.getAssetManager().setData({baseUrl:config.properties.baseUrl||""})),_renderer},getOption:function(name){return _renderer.getOption(name)},setOption:function(name,value){return _renderer.setOption(name,value)},setOptions:function(options){return _renderer.setOptions(options)},setContext:function($context){return _$previousContext=$context,containerHelper.setContext($context)},restoreContext:function(){containerHelper.setContext(_$previousContext),_$previousContext=null},load:function(qtiClasses,done){var renderer=_renderer||this.get();return renderer.load(function(){_.isFunction(done)&&done.apply(this,arguments)},qtiClasses)}},_renderer;return commonRenderer}),define("taoQtiItem/qtiItem/helper/simpleParser",["lodash","jquery","taoQtiItem/qtiItem/helper/util","taoQtiItem/qtiItem/core/Loader"],function(_,$,util,Loader){"use strict";function _getElementSelector(qtiClass,ns){return ns?ns+"\\:"+qtiClass+","+qtiClass:qtiClass}function getQtiClassFromXmlDom($node){let qtiClass=$node.prop("tagName").toLowerCase();return qtiClass=qtiClass.replace(/.*:/,""),_qtiClassNames[qtiClass]?_qtiClassNames[qtiClass]:qtiClass}function buildElement($elt){const qtiClass=getQtiClassFromXmlDom($elt),elt={qtiClass:qtiClass,serial:util.buildSerial(qtiClass+"_"),attributes:{}};return $.each($elt[0].attributes,function(){let attrName;this.specified&&(attrName=_qtiAttributesNames[this.name]||this.name,elt.attributes[attrName]=this.value)}),elt}function buildMath($elt,options){const elt=buildElement($elt);return elt.annotations={},$elt.find(_getElementSelector("annotation",options.ns.math)).each(function(){const $annotation=$(this),encoding=$annotation.attr("encoding");encoding&&(elt.annotations[encoding]=_.unescape($annotation.html())),$annotation.remove()}),elt.mathML=$elt.html(),elt.ns={name:"m",uri:"http://www.w3.org/1998/Math/MathML"},elt}function buildTooltip(targetHtml,contentId,contentHtml){const qtiClass="_tooltip";return{elements:{},qtiClass:"_tooltip",serial:util.buildSerial("_tooltip_"),attributes:{"aria-describedby":contentId},content:contentHtml,body:{elements:{},serial:util.buildSerial("container"),body:targetHtml}}}function parseTable($elt,elt,options){elt.body={body:"",elements:{}};const $parsedTable=parseContainer($elt,options);return elt.body.body=$parsedTable.body,elt.body.elements=$parsedTable.elements,elt}function parseFigure($elt,elt,options){elt.body={body:"",elements:{}};const $parsedFigure=parseContainer($elt,options);elt.body.body=$parsedFigure.body,elt.body.elements=$parsedFigure.elements;const $figcaption=$elt.find(_getElementSelector("figcaption",options.ns.figcaption));if($figcaption.length){const element=buildElement($figcaption);element.body={body:$figcaption.html(),elements:{}},elt.body.elements[element.serial]=element,$figcaption.replaceWith(_placeholder(element))}return elt.body.body=$elt.html(),elt}function parseContainer($container,options){const ret={serial:util.buildSerial("_container_"),body:"",elements:{}};return $container.find("table").each(function(){const $qtiElement=$(this);let element=buildElement($qtiElement);element=parseTable($qtiElement,element,options),ret.elements[element.serial]=element,$qtiElement.replaceWith(_placeholder(element))}),$container.find(_getElementSelector("figure",options.ns.figure)).each(function(){const $qtiElement=$(this);let element=buildElement($qtiElement);element=parseFigure($qtiElement,element,options),ret.elements[element.serial]=element,$qtiElement.replaceWith(_placeholder(element))}),$container.find(_getElementSelector("math",options.ns.math)).each(function(){const $qtiElement=$(this),element=buildMath($qtiElement,options);ret.elements[element.serial]=element,$qtiElement.replaceWith(_placeholder(element))}),$container.find(_getElementSelector("include",options.ns.include)).each(function(){const $qtiElement=$(this),element=buildElement($qtiElement);ret.elements[element.serial]=element,$qtiElement.replaceWith(_placeholder(element))}),$container.find("[data-role=\"tooltip-target\"]").each(function(){let $target=$(this),contentId=$target.attr("aria-describedBy"),element,$content,contentHtml;contentId&&($content=$container.find("#"+contentId),$content.length&&(contentHtml=$content.html(),element=buildTooltip($target.html(),contentId,contentHtml),ret.elements[element.serial]=element,$target.replaceWith(_placeholder(element)),$content.remove()))}),_.forEach(_parsableElements,function(qtiClass){$container.find(qtiClass).each(function(){const $qtiElement=$(this),element=buildElement($qtiElement);ret.elements[element.serial]=element,$qtiElement.replaceWith(_placeholder(element))})}),ret.body=$container.html(),ret}function _placeholder(element){return"{{"+element.serial+"}}"}_=_&&Object.prototype.hasOwnProperty.call(_,"default")?_["default"]:_,$=$&&Object.prototype.hasOwnProperty.call($,"default")?$["default"]:$,util=util&&Object.prototype.hasOwnProperty.call(util,"default")?util["default"]:util,Loader=Loader&&Object.prototype.hasOwnProperty.call(Loader,"default")?Loader["default"]:Loader;const _parsableElements=["img","object","printedVariable"],_qtiClassNames={rubricblock:"rubricBlock",printedvariable:"printedVariable"},_qtiAttributesNames={powerform:"powerForm",mappingindicator:"mappingIndicator"},_defaultOptions={ns:{math:"",include:"xi",table:"table",image:"img",object:"",figure:"qh5",figcaption:"qh5"},loaded:null,model:null};let parser;parser={parse:function(xmlStr,opts){const options=_.merge(_.clone(_defaultOptions),opts||{}),$container=$(xmlStr),element=buildElement($container),data=parseContainer($container,options);let loader;return _.isUndefined(data.body)||(element.body=data),_.isFunction(options.loaded)&&options.model&&(loader=new Loader().setClassesLocation(options.model),loader.loadAndBuildElement(element,options.loaded)),element}};var parser$1=parser;return parser$1}),define("taoQtiItem/qtiItem/helper/xincludeLoader",["jquery","lodash","taoQtiItem/qtiItem/helper/simpleParser","taoQtiItem/qtiItem/core/Loader"],function($,_,simpleParser,Loader){"use strict";function load(xinclude,baseUrl,callback){const href=xinclude.attr("href");if(href&&baseUrl){const fileUrl=`text!${baseUrl}${href}`;require.undef(fileUrl),require([fileUrl],function(stimulusXml){const $wrapper=$.parseXML(stimulusXml),$sampleXMLrootNode=$wrapper.children,$stimulus=$("").append($sampleXMLrootNode),mathNs="m",data=simpleParser.parse($stimulus,{ns:{math:mathNs}});new Loader().loadElement(xinclude,data,function(){if(_.isFunction(callback)){const loadedClasses=this.getLoadedClasses();loadedClasses.push("_container"),callback(xinclude,data,loadedClasses)}})},function(){callback(xinclude,!1,[])})}}$=$&&Object.prototype.hasOwnProperty.call($,"default")?$["default"]:$,_=_&&Object.prototype.hasOwnProperty.call(_,"default")?_["default"]:_,simpleParser=simpleParser&&Object.prototype.hasOwnProperty.call(simpleParser,"default")?simpleParser["default"]:simpleParser,Loader=Loader&&Object.prototype.hasOwnProperty.call(Loader,"default")?Loader["default"]:Loader;var xincludeLoader={load};return xincludeLoader}),define("taoQtiItem/qtiCreator/helper/xincludeRenderer",["module","context","lodash","taoQtiItem/qtiCreator/helper/commonRenderer","taoQtiItem/qtiItem/helper/xincludeLoader","core/moduleLoader"],function(module,context,_,commonRenderer,xincludeLoader,moduleLoader){"use strict";const moduleConfig=module.config();let xincludeHandlers=[];return moduleConfig.handlers&&moduleLoader({},_.isFunction).addList(moduleConfig.handlers).load(context.bundle).then(handlers=>xincludeHandlers=handlers),{render:function render(xincludeWidget,baseUrl,newHref){xincludeWidget.$container.attr("contenteditable",!1);const xinclude=xincludeWidget.element;newHref&&xinclude.attr("href",newHref),xincludeLoader.load(xinclude,baseUrl,function(xi,data,loadedClasses){if(data){const dataBody=data.body.body,hasClass=dataBody.match(/class="(?tao-\w+)?/);let className="";hasClass&&hasClass.groups&&hasClass.groups.className&&(className=hasClass.groups.className),commonRenderer.get().load(function(){_.forEach(xinclude.getComposingElements(),function(elt){elt.setRenderer(commonRenderer.get())}),xincludeWidget.refresh()},loadedClasses),xincludeHandlers.forEach(handler=>handler(xinclude.attr("href"),className,xi.serial))}else xinclude.removeAttr("href")})},getXincludeHandlers:function getXincludeHandlers(){return xincludeHandlers}}}),define("tpl!taoQtiItem/qtiCreator/tpl/toolbars/insertInteractionButton",["handlebars"],function(hb){return hb.template(function(Handlebars,depth0,helpers,partials,data){function program1(depth0,data){var buffer="",stack1,helper;return buffer+="\n data-sub-group=\"",(helper=helpers.subGroup)?stack1=helper.call(depth0,{hash:{},data:data}):(helper=depth0&&depth0.subGroup,stack1="function"===typeof helper?helper.call(depth0,{hash:{},data:data}):helper),buffer+=escapeExpression(stack1)+"\"\n",buffer}function program3(depth0,data){return"\n class=\"disabled\"\n title=\"element available in the final release\"\n"}function program5(depth0,data){var buffer="",stack1,helper;return buffer+="\n title=\"",(helper=helpers.title)?stack1=helper.call(depth0,{hash:{},data:data}):(helper=depth0&&depth0.title,stack1="function"===typeof helper?helper.call(depth0,{hash:{},data:data}):helper),buffer+=escapeExpression(stack1)+"\"\n",buffer}function program7(depth0,data){var buffer="",stack1,helper;return buffer+="\n \n ",buffer}function program9(depth0,data){var buffer="",stack1,helper;return buffer+="\n \n ",buffer}this.compilerInfo=[4,">= 1.0.0"],helpers=this.merge(helpers,Handlebars.helpers),data=data||{};var buffer="",functionType="function",escapeExpression=this.escapeExpression,self=this,stack1,helper;return buffer+="\n ",stack1=helpers["if"].call(depth0,depth0&&depth0.iconFont,{hash:{},inverse:self.program(9,program9,data),fn:self.program(7,program7,data),data:data}),(stack1||0===stack1)&&(buffer+=stack1),buffer+="\n\n
",(helper=helpers.short)?stack1=helper.call(depth0,{hash:{},data:data}):(helper=depth0&&depth0.short,stack1="function"===typeof helper?helper.call(depth0,{hash:{},data:data}):helper),buffer+=escapeExpression(stack1)+"
\n",buffer})}),define("tpl!taoQtiItem/qtiCreator/tpl/toolbars/insertInteractionGroup",["handlebars"],function(hb){return hb.template(function(Handlebars,depth0,helpers,partials,data){this.compilerInfo=[4,">= 1.0.0"],helpers=this.merge(helpers,Handlebars.helpers),data=data||{};var buffer="",functionType="function",escapeExpression=this.escapeExpression,stack1,helper;return buffer+="
\n

",(helper=helpers.label)?stack1=helper.call(depth0,{hash:{},data:data}):(helper=depth0&&depth0.label,stack1="function"===typeof helper?helper.call(depth0,{hash:{},data:data}):helper),buffer+=escapeExpression(stack1)+"

\n
\n
    \n
    \n
    ",buffer})}),define("tpl!taoQtiItem/qtiCreator/tpl/toolbars/tooltip",["handlebars"],function(hb){return hb.template(function(Handlebars,depth0,helpers,partials,data){this.compilerInfo=[4,">= 1.0.0"],helpers=this.merge(helpers,Handlebars.helpers),data=data||{};var buffer="",functionType="function",escapeExpression=this.escapeExpression,stack1,helper;return buffer+="
    \n \n
    \n ",(helper=helpers.message)?stack1=helper.call(depth0,{hash:{},data:data}):(helper=depth0&&depth0.message,stack1="function"===typeof helper?helper.call(depth0,{hash:{},data:data}):helper),buffer+=escapeExpression(stack1)+"\n
    \n
    ",buffer})}),define("taoQtiItem/qtiCreator/editor/interactionsToolbar",["jquery","lodash","i18n","ui/hider","tpl!taoQtiItem/qtiCreator/tpl/toolbars/insertInteractionButton","tpl!taoQtiItem/qtiCreator/tpl/toolbars/insertInteractionGroup","tpl!taoQtiItem/qtiCreator/tpl/toolbars/tooltip","ui/tooltip"],function($,_,__,hider,insertInteractionTpl,insertSectionTpl,tooltipTpl,tooltip){"use strict";function getGroupId(groupId){return groupId.replace(/\W+/g,"-").toLowerCase()}function getGroupSectionId(groupId){return"sidebar-left-section-"+getGroupId(groupId)}function addGroup($sidebar,groupId,groupLabel){const groupSectionId=getGroupSectionId(groupId),$section=$(insertSectionTpl({id:groupSectionId,label:groupLabel}));return $sidebar.append($section),$section}function create($sidebar,interactions){_.forEach(interactions,function(interactionAuthoringData){add($sidebar,interactionAuthoringData)}),buildSubGroups($sidebar),$sidebar.data("interaction-toolbar-ready",!0),$sidebar.trigger(_events.interactiontoolbarready)}function getGroup($sidebar,groupId){const groupSectionId=getGroupSectionId(groupId);return $sidebar.find("#"+groupSectionId)}function isReady($sidebar){return!!$sidebar.data("interaction-toolbar-ready")}function whenReady($sidebar){return new Promise(function(resolve){isReady($sidebar)?resolve():$sidebar.on(_events.interactiontoolbarready,function(){resolve()})})}function remove($sidebar,interactionClass){$sidebar.find("li[data-qti-class=\""+interactionClass+"\"]").remove()}function disable($sidebar,interactionClass){hider.hide($sidebar.find("li[data-qti-class=\""+interactionClass+"\"]"))}function enable($sidebar,interactionClass){hider.show($sidebar.find("li[data-qti-class=\""+interactionClass+"\"]"))}function exists($sidebar,interactionClass){return!!$sidebar.find("li[data-qti-class=\""+interactionClass+"\"]").length}function add($sidebar,interactionAuthoringData){if(exists($sidebar,interactionAuthoringData.qtiClass))throw"the interaction is already in the sidebar";const groupId=interactionAuthoringData.group,groupLabel=interactionAuthoringData.tags[0]||"",subGroupId=interactionAuthoringData.tags[1],tplData={qtiClass:interactionAuthoringData.qtiClass,disabled:!!interactionAuthoringData.disabled,title:interactionAuthoringData.description,iconFont:/^icon-/.test(interactionAuthoringData.icon),icon:interactionAuthoringData.icon,short:interactionAuthoringData.short,dev:!1};let $group=getGroup($sidebar,groupId);subGroupId&&_subgroups[subGroupId]&&(tplData.subGroup=subGroupId),$group.length||($group=addGroup($sidebar,groupId,groupLabel)),subGroupId&&_subgroups[subGroupId]&&(tplData.subGroup=subGroupId),$group.length||($group=addGroup($sidebar,groupId,groupLabel));let $interaction=$(insertInteractionTpl(tplData));return $group.find(".tool-list").append($interaction),$interaction}function buildSubGroups($sidebar){$sidebar.find("[data-sub-group]").each(function(){var $element=$(this),$section=$element.parents("section"),subGroup=$element.data("sub-group"),$subGroupPanel,$subGroupList,$cover;subGroup&&($subGroupPanel=$section.find(".sub-group."+subGroup),$subGroupList=$subGroupPanel.find(".tool-list"),!$subGroupPanel.length&&($subGroupPanel=$("
    ",{class:"panel clearfix sub-group "+subGroup}),$subGroupList=$("
      ",{class:"tool-list plain clearfix"}),$subGroupPanel.append($subGroupList),$section.append($subGroupPanel),$cover=$("
      ",{class:"sub-group-cover blocking"}),$subGroupPanel.append($cover),$subGroupPanel.data("cover",$cover)),$subGroupList.append($element))}),addInlineInteractionTooltip()}function addInlineInteractionTooltip(){var $inlineInteractionsPanel=$("#sidebar-left-section-inline-interactions .inline-interactions"),$tooltip=$(tooltipTpl({message:__("Inline interactions need to be inserted into a text block.")})),timer;$inlineInteractionsPanel.append($tooltip),tooltip.lookup($inlineInteractionsPanel),$tooltip.css({position:"absolute",zIndex:11,top:0,right:10}),$inlineInteractionsPanel.on("mouseenter",".sub-group-cover",function(){timer=setTimeout(function(){$tooltip.find("[data-tooltip]").data("$tooltip").show()},300)}).on("mouseleave",".sub-group-cover",function(){$tooltip.find("[data-tooltip]").data("$tooltip").hide(),clearTimeout(timer)})}var _customInteractionTag=__("Custom Interactions"),_subgroups={"inline-interactions":"Inline Interactions"},_events={interactiontoolbarready:"interactiontoolbarready.qti-widget"};return{create:create,add:add,exists:exists,addGroup:addGroup,getGroupId:getGroupId,getGroupSectionId:getGroupSectionId,getGroup:getGroup,isReady:isReady,whenReady:whenReady,remove:remove,disable:disable,enable:enable,getCustomInteractionTag:function(){return _customInteractionTag}}}),define("taoQtiItem/qtiCreator/helper/panel",["jquery","lodash","i18n","taoQtiItem/qtiItem/core/Element"],function($,_,__,Element){"use strict";var _getItemContainer=function(){return $("#item-editor-panel")},showPanel=function($panel,$fold){$panel.show(),openSections($panel.children("section")),$fold&&$fold.length&&closeSections($fold.children("section"))},initFormVisibilityListener=function(){$(document).off(".panel");var $itemContainer=_getItemContainer(),_staticElements={_tooltip:"Tooltip",figure:"Figure",img:"Image",object:"Media",rubricBlock:"Rubric Block",math:"Math",table:"Table",include:"Shared Stimulus",infoControl:"Student Tool"},$formInteractionPanel=$("#item-editor-interaction-property-bar"),$formChoicePanel=$("#item-editor-choice-property-bar"),$formResponsePanel=$("#item-editor-response-property-bar"),$formItemPanel=$("#item-editor-item-property-bar"),$formBodyElementPanel=$("#item-editor-body-element-property-bar"),$formTextBlockPanel=$("#item-editor-text-property-bar"),$formModalFeedbackPanel=$("#item-editor-modal-feedback-property-bar"),$formStylePanel=$("#item-style-editor-bar"),$appearanceToggler=$("#appearance-trigger"),$menuLabel=$appearanceToggler.find(".menu-label"),$itemIcon=$appearanceToggler.find(".icon-item"),$styleIcon=$appearanceToggler.find(".icon-style"),_toggleAppearanceEditor=function(active){active?($appearanceToggler.addClass("active"),$formStylePanel.show(),$formItemPanel.hide(),$itemContainer.trigger("styleedit"),showPanel($formStylePanel),$menuLabel.text($menuLabel.data("item")),$itemIcon.show(),$styleIcon.hide()):($appearanceToggler.removeClass("active"),$formStylePanel.hide(),showPanel($formItemPanel),$menuLabel.text($menuLabel.data("style")),$itemIcon.hide(),$styleIcon.show())};$appearanceToggler.on("click",function(){$appearanceToggler.hasClass("active")?_toggleAppearanceEditor(!1):_toggleAppearanceEditor(!0)}),_.delay(function(){showPanel($formItemPanel)},200),$(document).on("afterStateInit.qti-widget.panel",function(e,element,state){switch(state.name){case"active":_toggleAppearanceEditor(!1),Element.isA(element,"assessmentItem")||$formItemPanel.hide();var label=_staticElements[element.qtiClass];label?($formBodyElementPanel.find("h2").html(label+" "+__("Properties")),showPanel($formBodyElementPanel)):"_container"===element.qtiClass&&showPanel($formTextBlockPanel),"modalFeedback"===element.qtiClass&&(showPanel($formModalFeedbackPanel),$formResponsePanel.hide());break;case"question":showPanel($formInteractionPanel);break;case"answer":showPanel($formResponsePanel);break;case"choice":showPanel($formChoicePanel,$formInteractionPanel);break;case"sleep":_staticElements[element.qtiClass]?$formBodyElementPanel.hide():"_container"===element.qtiClass&&$formTextBlockPanel.hide(),Element.isA(element,"choice")||$itemContainer.find(".widget-box.edit-active").length||showPanel($formItemPanel)}}).on("afterStateExit.qti-widget.panel",function(e,element,state){switch(state.name){case"active":"modalFeedback"===element.qtiClass&&(showPanel($formResponsePanel),$formModalFeedbackPanel.hide());break;case"question":element.is("interaction")&&($formChoicePanel.hide(),$formInteractionPanel.hide());break;case"choice":$formChoicePanel.hide(),showPanel($formInteractionPanel);break;case"answer":$formResponsePanel.hide()}}).on("elementCreated.qti-widget.panel",function(e,data){"_container"===data.element.qtiClass&&enableSubGroup("inline-interactions")}).on("deleted.qti-widget.panel",function(e,data){"_container"===data.element.qtiClass&&toggleInlineInteractionGroup()})},toggleInlineInteractionGroup=function(){var $itemContainer=_getItemContainer();$itemContainer.find(".widget-textBlock").length?enableSubGroup("inline-interactions"):disableSubGroup("inline-interactions")},heading="h2",section="section",panel="hr, .panel",closed="closed",ns="accordion",initSidebarAccordion=function($sidebar){var $sections=$sidebar.find(section),$allPanels=$sidebar.children(panel).hide(),$allTriggers=$sidebar.find(heading);return!(0!==$allTriggers.length)||void($allTriggers.each(function(){var $heading=$(this),$section=$heading.parents(section),$panel=$section.children(panel),$closer=$("",{class:"icon-up"}),$opener=$("",{class:"icon-down"}),action=$panel.is(":visible")?"open":"close";($heading.append($closer).append($opener).addClass(closed),!$heading.hasClass("_accordion"))&&($heading.addClass("_accordion"),$panel.on("panelclose."+ns+" panelopen."+ns,function(e,args){var fn="panelclose"===e.type?"add":"remove";args.heading[fn+"Class"](closed)}),$panel.trigger("panel"+action+"."+ns,{heading:$heading}))}),$sections.each(function(){$(this).find(heading).on("click",function(e,args){var $heading=$(this),$panel=$heading.parents(section).children(panel),preserveOthers=!!(args&&args.preserveOthers),actions={close:"hide",open:"fadeIn"},forceState=!!(args&&args.forceState)&&args.forceState,action,classFn;forceState&&(classFn="open"===forceState?"addClass":"removeClass",$heading[classFn](closed)),action=$heading.hasClass(closed)?"open":"close",!1,$panel.trigger("panel"+action+"."+ns,{heading:$heading})[actions[action]]()})}))},_toggleSections=function(sections,preserveOthers,state){sections.each(function(){$(this).find(heading).trigger("click",{preserveOthers:preserveOthers,forceState:state})})},closeSections=function(sections,preserveOthers){_toggleSections(sections,!!preserveOthers,"close")},openSections=function(sections,preserveOthers){_toggleSections(sections,!!preserveOthers,"open")},_toggleSubGroup=function(subGroup,state){if(subGroup=$("."+subGroup),subGroup.length){var fn="disable"===state?"addClass":"removeClass";subGroup.data("cover")[fn]("blocking")}},enableSubGroup=function(subGroup){_toggleSubGroup(subGroup,"enable")},disableSubGroup=function(subGroup){_toggleSubGroup(subGroup,"disable")};return{initFormVisibilityListener:initFormVisibilityListener,showPanel:showPanel,toggleInlineInteractionGroup:toggleInlineInteractionGroup,initSidebarAccordion:initSidebarAccordion,openSections:openSections,closeSections:closeSections,enableSubGroup:enableSubGroup,disableSubGroup:disableSubGroup}}),define("taoQtiItem/qtiCreator/editor/interactionsPanel",["taoQtiItem/qtiCreator/editor/interactionsToolbar","taoQtiItem/qtiCreator/helper/panel","taoQtiItem/qtiCreator/helper/qtiElements","taoQtiItem/portableElementRegistry/ciRegistry","services/features"],function(interactionsToolbar,panel,qtiElements,ciRegistry,features){"use strict";return function setUpInteractionPanel($container){const interactions=qtiElements.getAvailableAuthoringElements(),liquidsInteractionAvailable=features.isVisible("taoQtiItem/creator/interaction/pci/liquidsInteraction"),liquidsInteractionId="liquidsInteraction";for(const typeId in ciRegistry.getAllVersions()){if(typeId===liquidsInteractionId&&!liquidsInteractionAvailable)continue;const data=ciRegistry.getAuthoringData(typeId,{enabledOnly:!0});data&&data.tags&&data.tags[0]===interactionsToolbar.getCustomInteractionTag()&&(interactions[data.qtiClass]=data)}interactionsToolbar.create($container,interactions),panel.initSidebarAccordion($container),panel.closeSections($container.find("section")),panel.openSections($container.find("#sidebar-left-section-common-interactions"),!1),panel.toggleInlineInteractionGroup()}}),define("tpl!taoQtiItem/qtiCreator/tpl/toolbars/cssToggler",["handlebars"],function(hb){return hb.template(function(Handlebars,depth0,helpers,partials,data){this.compilerInfo=[4,">= 1.0.0"],helpers=this.merge(helpers,Handlebars.helpers),data=data||{};var buffer="",functionType="function",escapeExpression=this.escapeExpression,stack1,helper;return buffer+="
    • \n \n ",(helper=helpers.label)?stack1=helper.call(depth0,{hash:{},data:data}):(helper=depth0&&depth0.label,stack1="function"===typeof helper?helper.call(depth0,{hash:{},data:data}):helper),buffer+=escapeExpression(stack1)+"\n \n \n \n
    • ",buffer})}),define("taoQtiItem/qtiCreator/editor/styleEditor/styleEditor",["jquery","lodash","i18n","util/urlParser","core/promise","tpl!taoQtiItem/qtiCreator/tpl/toolbars/cssToggler","jquery.fileDownload"],function($,_,__,UrlParser,Promise,cssTpl){"use strict";let itemConfig,globalConfig;const _getUri=function(action){return globalConfig[`${action}CssUrl`]},_basename=function(path){return path.substring(path.lastIndexOf("/")+1)};let style={};const $styleElem=function(){let styleElem=$("#item-editor-user-styles");return styleElem.length?styleElem.empty():(styleElem=$("