diff --git a/main/exercice/export/exercise_import.inc.php b/main/exercice/export/exercise_import.inc.php index a3a66eb2e6..6454593efb 100755 --- a/main/exercice/export/exercise_import.inc.php +++ b/main/exercice/export/exercise_import.inc.php @@ -65,7 +65,7 @@ function get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath) /** * Imports an exercise in QTI format if the XML structure can be found in it * @param array $file - * @return an array as a backlog of what was really imported, and error or debug messages to display + * @return string|array as a backlog of what was really imported, and error or debug messages to display */ function import_exercise($file) { @@ -164,19 +164,30 @@ function import_exercise($file) $question = new Ims2Question(); $question->type = $question_array['type']; $question->setAnswer(); - $question->updateTitle(formatText($question_array['title'])); + if (strlen($question_array['title']) < 50) { + $question->updateTitle(formatText(strip_tags($question_array['title'])) . '...'); + } else { + $question->updateTitle(formatText(substr(strip_tags($question_array['title']), 0, 50))); + $question->updateDescription($question_array['title']); + } //$question->updateDescription($question_array['title']); - $type = $question->selectType(); - $question->type = constant($type); $question->save($last_exercise_id); $last_question_id = $question->selectId(); //3. Create answer $answer = new Answer($last_question_id); $answer->new_nbrAnswers = count($question_array['answer']); $totalCorrectWeight = 0; + $j = 1; + $matchAnswerIds = array(); foreach ($question_array['answer'] as $key => $answers) { - $split = explode('_', $key); - $i = $split[1]; + if (preg_match('/_/', $key)) { + $split = explode('_', $key); + $i = $split[1]; + } else { + $i = $j; + $j++; + $matchAnswerIds[$key] = $j; + } // Answer $answer->new_answer[$i] = formatText($answers['value']); // Comment @@ -191,7 +202,7 @@ function import_exercise($file) } $answer->new_weighting[$i] = $question_array['weighting'][$key]; if ($answer->new_correct[$i]) { - $totalCorrectWeight = $answer->new_weighting[$i]; + $totalCorrectWeight += $answer->new_weighting[$i]; } } $question->updateWeighting($totalCorrectWeight); @@ -241,6 +252,12 @@ function qti_parse_file($exercisePath, $file, $questionFile) //parse XML question file $data = str_replace(array('

', '

', '', ''), '', $data); + $qtiVersion = array(); + $match = preg_match('/ims_qtiasiv(\d)p(\d)/', $data, $qtiVersion); + $qtiMainVersion = 2; //by default, assume QTI version 2 + if ($match) { + $qtiMainVersion = $qtiVersion[1]; + } //used global variable start values declaration : @@ -264,8 +281,13 @@ function qti_parse_file($exercisePath, $file, $questionFile) $xml_parser = xml_parser_create(); xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, false); - xml_set_element_handler($xml_parser, 'startElement', 'endElement'); - xml_set_character_data_handler($xml_parser, 'elementData'); + if ($qtiMainVersion == 1) { + xml_set_element_handler($xml_parser, 'startElementQti1', 'endElementQti1'); + xml_set_character_data_handler($xml_parser, 'elementDataQti1'); + } else { + xml_set_element_handler($xml_parser, 'startElementQti2', 'endElementQti2'); + xml_set_character_data_handler($xml_parser, 'elementDataQti2'); + } if (!xml_parse($xml_parser, $data, feof($fp))) { // if reading of the xml file in not successful : // set errorFound, set error msg, break while statement @@ -299,7 +321,7 @@ function qti_parse_file($exercisePath, $file, $questionFile) * @param string $name name of the element * @param array $attributes */ -function startElement($parser, $name, $attributes) +function startElementQti2($parser, $name, $attributes) { global $element_pile; global $exercise_info; @@ -340,7 +362,6 @@ function startElement($parser, $name, $attributes) //we first save with claroline tags ,then when the answer will be parsed, the claroline tags will be replaced if ($current_element == 'INLINECHOICEINTERACTION') { - $current_question_item_body .= "**claroline_start**" . $attributes['RESPONSEIDENTIFIER'] . "**claroline_end**"; } if ($current_element == 'TEXTENTRYINTERACTION') { @@ -353,6 +374,7 @@ function startElement($parser, $name, $attributes) } } } + switch ($current_element) { case 'ASSESSMENTITEM': //retrieve current question @@ -372,18 +394,18 @@ function startElement($parser, $name, $attributes) case 'RESPONSEDECLARATION': // Retrieve question type if ("multiple" == $attributes['CARDINALITY']) { - $exercise_info['question'][$current_question_ident]['type'] = 'MCMA'; + $exercise_info['question'][$current_question_ident]['type'] = MCMA; $cardinality = 'multiple'; } if ("single" == $attributes['CARDINALITY']) { - $exercise_info['question'][$current_question_ident]['type'] = 'MCUA'; + $exercise_info['question'][$current_question_ident]['type'] = MCUA; $cardinality = 'single'; } //needed for FIB $current_answer_id = $attributes['IDENTIFIER']; break; case 'INLINECHOICEINTERACTION': - $exercise_info['question'][$current_question_ident]['type'] = 'FIB'; + $exercise_info['question'][$current_question_ident]['type'] = FIB; $exercise_info['question'][$current_question_ident]['subtype'] = 'LISTBOX_FILL'; $current_answer_id = $attributes['RESPONSEIDENTIFIER']; break; @@ -391,13 +413,13 @@ function startElement($parser, $name, $attributes) $current_inlinechoice_id = $attributes['IDENTIFIER']; break; case 'TEXTENTRYINTERACTION': - $exercise_info['question'][$current_question_ident]['type'] = 'FIB'; + $exercise_info['question'][$current_question_ident]['type'] = FIB; $exercise_info['question'][$current_question_ident]['subtype'] = 'TEXTFIELD_FILL'; $exercise_info['question'][$current_question_ident]['response_text'] = $current_question_item_body; //replace claroline tags break; case 'MATCHINTERACTION': - $exercise_info['question'][$current_question_ident]['type'] = 'MATCHING'; + $exercise_info['question'][$current_question_ident]['type'] = MATCHING; break; case 'SIMPLEMATCHSET': if (!isset($current_match_set)) { @@ -446,7 +468,7 @@ function startElement($parser, $name, $attributes) * @param $parser xml parser created with "xml_parser_create()" * @param $name name of the element */ -function endElement($parser, $name) +function endElementQti2($parser, $name) { global $element_pile; global $exercise_info; @@ -467,7 +489,7 @@ function endElement($parser, $name) switch ($name) { case 'ITEMBODY': $record_item_body = false; - if ($exercise_info['question'][$current_question_ident]['type'] == 'FIB') { + if ($exercise_info['question'][$current_question_ident]['type'] == FIB) { $exercise_info['question'][$current_question_ident]['response_text'] = $current_question_item_body; } else { $exercise_info['question'][$current_question_ident]['statement'] = $current_question_item_body; @@ -481,7 +503,7 @@ function endElement($parser, $name) * @param $parser * @param $data */ -function elementData($parser, $data) +function elementDataQti2($parser, $data) { global $element_pile; global $exercise_info; @@ -561,3 +583,319 @@ function elementData($parser, $data) break; } } + +/** + * Function used by the SAX xml parser when the parser meets a opening tag for QTI1 + * + * @param object $parser xml parser created with "xml_parser_create()" + * @param string $name name of the element + * @param array $attributes + */ +function startElementQti1($parser, $name, $attributes) +{ + global $element_pile; + global $exercise_info; + global $current_question_ident; + global $current_answer_id; + global $current_match_set; + global $currentAssociableChoice; + global $current_question_item_body; + global $record_item_body; + global $non_HTML_tag_to_avoid; + global $current_inlinechoice_id; + global $cardinality; + global $questionTempDir; + global $lastLabelFieldName; + global $lastLabelFieldValue; + + array_push($element_pile, $name); + $current_element = end($element_pile); + if (sizeof($element_pile) >= 2) { + $parent_element = $element_pile[sizeof($element_pile) - 2]; + } else { + $parent_element = ""; + } + if (sizeof($element_pile) >= 3) { + $grand_parent_element = $element_pile[sizeof($element_pile) - 3]; + } else { + $grand_parent_element = ""; + } + if (sizeof($element_pile) >= 4) { + $great_grand_parent_element = $element_pile[sizeof($element_pile) - 4]; + } else { + $great_grand_parent_element = ""; + } + + + if ($record_item_body) { + + if ((!in_array($current_element, $non_HTML_tag_to_avoid))) { + $current_question_item_body .= "<" . $name; + foreach ($attributes as $attribute_name => $attribute_value) { + $current_question_item_body .= " " . $attribute_name . "=\"" . $attribute_value . "\""; + } + $current_question_item_body .= ">"; + } else { + //in case of FIB question, we replace the IMS-QTI tag b y the correct answer between "[" "]", + //we first save with claroline tags ,then when the answer will be parsed, the claroline tags will be replaced + + if ($current_element == 'INLINECHOICEINTERACTION') { + $current_question_item_body .= "**claroline_start**" . $attributes['RESPONSEIDENTIFIER'] . "**claroline_end**"; + } + if ($current_element == 'TEXTENTRYINTERACTION') { + $correct_answer_value = $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id]; + $current_question_item_body .= "[" . $correct_answer_value . "]"; + + } + if ($current_element == 'BR') { + $current_question_item_body .= "
"; + } + } + } + + switch ($current_element) { + case 'ASSESSMENT': + // This is the assessment element: we don't care, we just want questions + if (!empty($attributes['TITLE'])) { + $exercise_info['name'] = $attributes['TITLE']; + } + break; + case 'ITEM': + //retrieve current question + $current_question_ident = $attributes['IDENT']; + $exercise_info['question'][$current_question_ident] = array(); + $exercise_info['question'][$current_question_ident]['answer'] = array(); + $exercise_info['question'][$current_question_ident]['correct_answers'] = array(); + //$exercise_info['question'][$current_question_ident]['title'] = $attributes['TITLE']; + $exercise_info['question'][$current_question_ident]['tempdir'] = $questionTempDir; + break; + case 'SECTION': + //retrieve exercise name + //if (isset($attributes['TITLE']) && !empty($attributes['TITLE'])) { + // $exercise_info['name'] = $attributes['TITLE']; + //} + break; + case 'RESPONSE_LID': + // Retrieve question type + if ("multiple" == strtolower($attributes['RCARDINALITY'])) { + $cardinality = 'multiple'; + } + if ("single" == strtolower($attributes['RCARDINALITY'])) { + $cardinality = 'single'; + } + //needed for FIB + $current_answer_id = $attributes['IDENT']; + $current_question_item_body = ''; + break; + case 'RENDER_CHOICE'; + break; + case 'RESPONSE_LABEL': + if (!empty($attributes['IDENT'])) { + $current_answer_id = $attributes['IDENT']; + //set the placeholder for the answer to come (in endElementQti1) + $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id] = ''; + } + break; + case 'DECVAR': + if ($parent_element == 'OUTCOMES' && $grand_parent_element == 'RESPROCESSING') { + // The following attributes are available + //$attributes['VARTYPE']; + //$attributes['DEFAULTVAL']; + //$attributes['MINVALUE']; + //$attributes['MAXVALUE']; + } + break; + case 'VAREQUAL': + if ($parent_element == 'CONDITIONVAR' && $grand_parent_element == 'RESPCONDITION') { + // The following attributes are available + //$attributes['RESPIDENT'] + } + break; + case 'SETVAR': + if ($parent_element == 'RESPCONDITION') { + // The following attributes are available + //$attributes['ACTION'] + } + break; + case 'IMG': + //$exercise_info['question'][$current_question_ident]['attached_file_url'] = $attributes['SRC']; + break; + case 'MATTEXT': + if ($parent_element == 'MATERIAL') { + if ($grand_parent_element == 'PRESENTATION') { + $exercise_info['question'][$current_question_ident]['title'] = $current_question_item_body; + } + } + } +} + +/** + * Function used by the SAX xml parser when the parser meets a closing tag for QTI1 + * + * @param object $parser xml parser created with "xml_parser_create()" + * @param string $name name of the element + * @param array $attributes The element attributes + */ +function endElementQti1($parser, $name, $attributes) +{ + global $element_pile; + global $exercise_info; + global $current_question_ident; + global $record_item_body; + global $current_question_item_body; + global $non_HTML_tag_to_avoid; + global $cardinality; + global $lastLabelFieldName; + global $lastLabelFieldValue; + + $current_element = end($element_pile); + if (sizeof($element_pile) >= 2) { + $parent_element = $element_pile[sizeof($element_pile) - 2]; + } else { + $parent_element = ""; + } + if (sizeof($element_pile) >= 3) { + $grand_parent_element = $element_pile[sizeof($element_pile) - 3]; + } else { + $grand_parent_element = ""; + } + if (sizeof($element_pile) >= 4) { + $great_grand_parent_element = $element_pile[sizeof($element_pile) - 4]; + } else { + $great_grand_parent_element = ""; + } + + //treat the record of the full content of itembody tag : + + if ($record_item_body && (!in_array($current_element, $non_HTML_tag_to_avoid))) { + $current_question_item_body .= ""; + } + + switch ($name) { + case 'MATTEXT': + if ($parent_element == 'MATERIAL') { + if ($grand_parent_element == 'PRESENTATION') { + $exercise_info['question'][$current_question_ident]['title'] = $current_question_item_body; + $current_question_item_body = ''; + } elseif ($grand_parent_element == 'RESPONSE_LABEL') { + $last = ''; + foreach ($exercise_info['question'][$current_question_ident]['answer'] as $key => $value) { + $last = $key; + } + $exercise_info['question'][$current_question_ident]['answer'][$last]['value'] = $current_question_item_body; + $current_question_item_body = ''; + } + } + case 'RESPONSE_LID': + // Retrieve question type + if (!isset($exercise_info['question'][$current_question_ident]['type'])) { + if ("multiple" == strtolower($attributes['RCARDINALITY'])) { + $exercise_info['question'][$current_question_ident]['type'] = MCMA; + } + if ("single" == strtolower($attributes['RCARDINALITY'])) { + $exercise_info['question'][$current_question_ident]['type'] = MCUA; + } + } + $current_question_item_body = ''; + //needed for FIB + $current_answer_id = $attributes['IDENT']; + break; + case 'ITEMMETADATA': + $current_question_item_body = ''; + break; + } + array_pop($element_pile); +} + +/** + * QTI1 element parser + * @param $parser + * @param $data + */ +function elementDataQti1($parser, $data) +{ + global $element_pile; + global $exercise_info; + global $current_question_ident; + global $current_answer_id; + global $current_match_set; + global $currentAssociableChoice; + global $current_question_item_body; + global $record_item_body; + global $non_HTML_tag_to_avoid; + global $current_inlinechoice_id; + global $cardinality; + global $lastLabelFieldName; + global $lastLabelFieldValue; + + $current_element = end($element_pile); + if (sizeof($element_pile) >= 2) { + $parent_element = $element_pile[sizeof($element_pile) - 2]; + } else { + $parent_element = ""; + } + //treat the record of the full content of itembody tag (needed for question statment and/or FIB text: + + if ($record_item_body && (!in_array($current_element, $non_HTML_tag_to_avoid))) { + $current_question_item_body .= $data; + } + + switch ($current_element) { + case 'FIELDLABEL': + if (!empty($data)) { + $lastLabelFieldName = $current_element; + $lastLabelFieldValue = $data; + } + case 'FIELDENTRY': + $current_question_item_body = $data; + switch ($lastLabelFieldValue) { + case 'cc_profile': + // The following values might be proprietary in MATRIX software. No specific reference + // in QTI doc: http://www.imsglobal.org/question/qtiv1p2/imsqti_asi_infov1p2.html#1415855 + switch ($data) { + case 'cc.true_false.v0p1': + //this is a true-false question (translated to multiple choice in Chamilo because true-false comes with "I don't know") + $exercise_info['question'][$current_question_ident]['type'] = MCUA; + break; + case 'cc.multiple_choice.v0p1': + //this is a multiple choice (unique answer) question + $exercise_info['question'][$current_question_ident]['type'] = MCUA; + break; + case 'cc.multiple_response.v0p1': + //this is a multiple choice (unique answer) question + $exercise_info['question'][$current_question_ident]['type'] = MCMA; + break; + } + break; + case 'cc_weighting': + //defines the total weight of the question + $exercise_info['question'][$current_question_ident]['default_weighting'] = $lastLabelFieldValue; + break; + case 'assessment_question_identifierref': + //placeholder - not used yet + // Possible values are not defined by qti v1.2 + break; + } + break; + case 'MATTEXT': + if (!empty($current_question_item_body)) { + $current_question_item_body .= $data; + } else { + $current_question_item_body = $data; + } + break; + case 'VAREQUAL': + $lastLabelFieldName = 'VAREQUAL'; + $lastLabelFieldValue = $data; + break; + case 'SETVAR': + if ($parent_element == 'RESPCONDITION') { + // The following attributes are available + //$attributes['ACTION'] + $exercise_info['question'][$current_question_ident]['correct_answers'][] = $lastLabelFieldValue; + $exercise_info['question'][$current_question_ident]['weighting'][$lastLabelFieldValue] = $data; + } + break; + + } +}