private function generateLearnosityToQtiDocumentation()
 {
     $questionTypeDocumentation = [];
     $responsesSchemas = $this->schemasService->getResponsesSchemas();
     foreach ($responsesSchemas as $questionType => $data) {
         if (in_array($questionType, QtiExportConstant::$supportedQuestionTypes)) {
             /** @var QuestionTypeDocumentationInterface $mapperClass */
             $mapperClass = 'LearnosityQti\\Processors\\QtiV2\\Out\\Documentation\\QuestionTypes\\' . ucfirst($questionType) . 'Documentation';
             $documentation = $mapperClass::getDocumentation();
             foreach (array_keys($this->generateAtributeTable($data['attributes'])) as $flattenedAttributeName) {
                 // TODO: Need to check new or non-existing attribute name in case our schemas change
                 if (!in_array($flattenedAttributeName, array_keys($documentation))) {
                     $documentation[$flattenedAttributeName] = LearnosityDoc::none();
                 }
             }
             // TODO: Hack here, hide all the `validation` attributes
             $documentationToDisplay = [];
             foreach ($documentation as $attributeName => $doc) {
                 if (!StringUtil::startsWith($attributeName, 'validation')) {
                     $documentationToDisplay[$attributeName] = $doc;
                 }
             }
             $questionTypeDocumentation[$questionType] = ['mapping' => $documentationToDisplay, 'introduction' => $mapperClass::getIntroductionNotes()];
         }
     }
     return ['questionTypes' => $questionTypeDocumentation, 'unsupportedQuestionTypes' => array_keys(array_diff_key($responsesSchemas, $questionTypeDocumentation))];
 }
 public function convert(Manifest $manifest, array $rules = [])
 {
     $activityReference = $manifest->getIdentifier();
     $tagsWriter = new TagsWriter();
     // Does not handle submanifest tyvm!
     if (!empty($manifest->getManifest())) {
         LogService::log('Does not handle sub-manifest element thus it is ignored');
     }
     // Atm, we only have tags rules and this need to be validated and user shall be provided with a nice error message
     // TODO: Validation need to be done in future
     $tagRules = isset($rules['tags']) ? $rules['tags'] : [];
     // Let's map package metadatas as activity tags
     // We can write custom replacer or remover to fix the messy `identifier:catalog:` afterwards
     $activityTags = [];
     $metadatas = $manifest->getMetadata();
     if (!empty($metadatas)) {
         $tags = $tagsWriter->convert($metadatas, $tagRules);
         if (!empty($tags)) {
             $activityTags = ['reference' => $activityReference, 'tags' => $tags];
         }
     }
     $itemReferences = [];
     $itemsTags = [];
     // Build item reference and item tags JSON
     $organisations = $manifest->getOrganizations();
     if (!empty($organisations)) {
         foreach ($organisations as $organisation) {
             foreach ($organisation->getItems() as $item) {
                 $itemReferences[] = $item->getIdentifier();
             }
         }
     }
     // Build item reference and item tags JSON
     $resources = $manifest->getResources();
     if (!empty($resources)) {
         foreach ($resources as $resource) {
             // Just add `item` resource as items, and leave css and any other resources alone
             if (StringUtil::startsWith($resource->getType(), 'imsqti_item')) {
                 /** @var Resource $resource */
                 $itemReference = $resource->getIdentifier();
                 $itemReferences[] = $itemReference;
                 $tags = $tagsWriter->convert($resource->getMetadata(), $tagRules);
                 if (!empty($tags)) {
                     $itemsTags[] = ['reference' => $itemReference, 'tags' => $tags];
                 }
             }
         }
     }
     // Build activity JSON
     $activity = ['reference' => $activityReference, 'data' => ['items' => array_values(array_unique($itemReferences))], 'status' => 'published'];
     // Obvious here that these `items` hasn't and wouldn't be validated against
     // Should do it later by the function that calls this
     return [$activity, $activityTags, $itemsTags];
 }
Example #3
0
 public function matchAndRemoveByKey($pathPattern)
 {
     $matches = [];
     foreach ($this->flattenedMetadatas as $key => $value) {
         list($valueMatched, $valueMatches) = StringUtil::matchString($pathPattern, $key);
         if ($valueMatched === true) {
             $matches[$key] = ['value' => $value, 'valueMatched' => $valueMatched, 'valueMatches' => $valueMatches];
         }
     }
     $this->removeKeys(array_keys($matches));
     return $matches;
 }
    public function testMappingMcqQuestion()
    {
        $questionJson = $this->getFixtureFileContents('learnosityjsons/item_mcq.json');
        $question = json_decode($questionJson, true);
        list($xmlString, $messages) = Converter::convertLearnosityToQtiItem($question);
        $this->assertNotNull($xmlString);
        $this->assertTrue(StringUtil::startsWith($xmlString, '<?xml version="1.0" encoding="UTF-8"?>
<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_v2p1"'));
        $document = new XmlDocument();
        $document->loadFromString($xmlString);
        $this->assertNotNull($document);
    }
 private function matchParameters(array $parameters, array $matchedGroup)
 {
     $matchedParameters = [];
     foreach ($parameters as $param => $paramPath) {
         foreach ($matchedGroup as $group) {
             $endString = $group['endString'];
             $value = $group['value'];
             // Only grab the first match!
             list($matched, $parammatches) = StringUtil::matchString($paramPath, $endString);
             if ($matched === true) {
                 $matchedParameters[$param] = $value;
             }
         }
     }
     return $matchedParameters;
 }
Example #6
0
 public function convert(item $item, array $questions)
 {
     // Make sure we clean up the log
     LogService::flush();
     // Try to build the identifier using item `reference`
     // Otherwise, generate an alternative identifier and store the original reference as `label`
     $itemReference = $item->get_reference();
     $itemIdentifier = Format::isIdentifier($itemReference, false) ? $itemReference : 'ITEM_' . StringUtil::generateRandomString(12);
     if ($itemReference !== $itemIdentifier) {
         LogService::log("The item `reference` ({$itemReference}) is not a valid identifier, thus can not be used for `assessmentItem` identifier. " . "Replaced it with randomly generated `{$itemIdentifier}` and stored the original `reference` as `label` attribute");
     }
     $builder = new AssessmentItemBuilder();
     $assessmentItem = $builder->build($itemIdentifier, $itemReference, $questions, $item->get_content());
     $xml = new XmlDocument();
     $xml->setDocumentComponent($assessmentItem);
     // Flush out all the error messages stored in this static class, also ensure they are unique
     $messages = array_values(array_unique(LogService::flush()));
     return [$xml->saveToString(true), $messages];
 }
 public function testWithMapResponseValidationMissingAssociableIdentifier()
 {
     $bgObject = new Object('http://img.png', 'image/png');
     $bgObject->setWidth(100);
     $bgObject->setHeight(200);
     $testInteraction = GraphicGapInteractionBuilder::build('testInteraction', $bgObject, ['A' => 'img_A.png', 'B' => 'img_B.png', 'C' => 'img_C.png'], ['G1' => [0, 0, 10, 10], 'G2' => [0, 0, 10, 10]]);
     $responseProcessingTemplate = ResponseProcessingTemplate::mapResponse();
     $responseDeclaration = ResponseDeclarationBuilder::buildWithMapping('testIdentifier', ['A G1' => [1, false]], 'QtiDirectedPair');
     $mapper = new GraphicGapMatchInteractionMapper($testInteraction, $responseDeclaration, $responseProcessingTemplate);
     /** @var imageclozeassociation $q */
     $q = $mapper->getQuestionType();
     $this->assertEquals('imageclozeassociation', $q->get_type());
     $this->assertEquals(['<img src="img_A.png"/>', '<img src="img_B.png"/>', '<img src="img_C.png"/>'], $q->get_possible_responses());
     $this->assertFalse($q->get_duplicate_responses());
     $this->assertNull($q->get_validation());
     $containsWarning = false;
     foreach (LogService::read() as $message) {
         if (StringUtil::contains($message, 'Amount of gap identifiers 2 does not match the amount 1 for <responseDeclaration>')) {
             return true;
         }
     }
     $this->assertTrue($containsWarning);
 }
 private function buildItemBodyWithItemContent(array $interactions, $content)
 {
     // Map <itemBody>
     // TODO: Wrap these `content` stuff in a div
     // TODO: to avoid QtiComponentIterator bug ignoring 2nd element with empty content
     $contentCollection = QtiMarshallerUtil::unmarshallElement($content);
     $wrapperCollection = new FlowCollection();
     foreach ($contentCollection as $component) {
         $wrapperCollection->attach($component);
     }
     $divWrapper = new Div();
     $divWrapper->setContent($wrapperCollection);
     // Iterate through these elements and try to replace every single question `span` with its interaction equivalent
     $iterator = $divWrapper->getIterator();
     foreach ($iterator as $component) {
         if ($component instanceof Span && StringUtil::contains($component->getClass(), 'learnosity-response')) {
             $currentContainer = $iterator->getCurrentContainer();
             $questionReference = trim(str_replace('learnosity-response', '', $component->getClass()));
             $questionReference = trim(str_replace('question-', '', $questionReference));
             // Build the actual interaction
             $interaction = $interactions[$questionReference]['interaction'];
             $content = new FlowCollection();
             if (isset($interactions[$questionReference]['extraContent'])) {
                 $content->attach($interactions[$questionReference]['extraContent']);
             }
             $content->attach($interaction);
             $replacement = ContentCollectionBuilder::buildContent($currentContainer, $content)->current();
             $currentContainer->getComponents()->replace($component, $replacement);
         }
     }
     // Extract the actual content from the div wrapper and add that to our <itemBody>
     $componentsWithinDiv = $divWrapper->getComponents();
     $itemBody = new ItemBody();
     $itemBody->setContent(ContentCollectionBuilder::buildBlockCollectionContent($componentsWithinDiv));
     return $itemBody;
 }
Example #9
0
 public static function convertQtiItemToLearnosity($xmlString, $baseAssetsUrl = '', $validate = true)
 {
     $itemMapper = AppContainer::getApplicationContainer()->get('qtiv2_item_mapper');
     $itemWriter = AppContainer::getApplicationContainer()->get('learnosity_item_writer');
     $questionWriter = AppContainer::getApplicationContainer()->get('learnosity_question_writer');
     // Parse `em
     try {
         list($item, $questions, $exceptions) = $itemMapper->parse($xmlString, $validate);
     } catch (XmlStorageException $e) {
         // Check invalid schema error message and intercept to rethrow as known `InvalidQtiException` exception
         $exceptionMessage = $e->getMessage();
         if (StringUtil::startsWith($exceptionMessage, 'The document could not be validated with XML Schema')) {
             $exceptionMessage = preg_replace('/The document could not be validated with schema(.*)/', 'The document could not be validated with standard QTI schema: ', $exceptionMessage);
             throw new InvalidQtiException($exceptionMessage);
         } else {
             throw $e;
         }
     }
     // Conversion to JSON
     $itemData = [];
     if ($item instanceof item) {
         $itemData = $itemWriter->convert($item);
     }
     $questionsData = [];
     if (is_array($questions)) {
         foreach ($questions as $question) {
             $questionsData[] = $questionWriter->convert($question);
         }
     }
     return [$itemData, $questionsData, $exceptions];
 }
 private function map(Question $question)
 {
     $type = $question->get_type();
     if (!in_array($type, Constants::$supportedQuestionTypes)) {
         throw new MappingException("Question type `{$type}` not yet supported to be mapped to QTI");
     }
     $clazz = new \ReflectionClass(self::MAPPER_CLASS_BASE . ucfirst($type . 'Mapper'));
     $questionTypeMapper = $clazz->newInstance();
     // Try to use question `reference` as identifier
     // Otherwise, generate an alternative identifier and store the original reference as `label` to be passed in
     $questionReference = $question->get_reference();
     $interactionIdentifier = Format::isIdentifier($questionReference, false) ? $questionReference : strtoupper($type) . '_' . StringUtil::generateRandomString(12);
     if ($interactionIdentifier !== $questionReference) {
         LogService::log("The question `reference` ({$questionReference}) is not a valid identifier. " . "Replaced it with randomly generated `{$interactionIdentifier}` and stored the original `reference` as `label` attribute");
     }
     $result = $questionTypeMapper->convert($question->get_data(), $interactionIdentifier, $questionReference);
     $result[] = $questionTypeMapper->getExtraContent();
     return $result;
 }
 private function getFeatureReferenceFromClassName($classname)
 {
     // Parse classname, ie `learnosity-feature feature-DEMOFEATURE123`
     // Then, return `DEMOFEATURE123`
     $parts = preg_split('/\\s+/', $classname);
     foreach ($parts as $part) {
         if (StringUtil::startsWith(strtolower($part), 'feature-')) {
             return explode('-', $part)[1];
         }
     }
     // TODO: throw exception
     return null;
 }