|
@@ -1,32 +1,13 @@
|
|
|
<?php
|
|
|
+/* For licensing terms, see /license.txt */
|
|
|
/**
|
|
|
* @copyright (c) 2001-2006 Universite catholique de Louvain (UCL)
|
|
|
- *
|
|
|
- * @license http://www.gnu.org/copyleft/gpl.html (GPL) GENERAL PUBLIC LICENSE
|
|
|
- *
|
|
|
* @package chamilo.exercise
|
|
|
- * @deprecated
|
|
|
- *
|
|
|
* @author claro team <cvs@claroline.net>
|
|
|
* @author Guillaume Lederer <guillaume@claroline.net>
|
|
|
+ * @author Yannick Warnier <yannick.warnier@beeznest.com>
|
|
|
*/
|
|
|
|
|
|
-/**
|
|
|
- * function to create a temporary directory (SAME AS IN MODULE ADMIN)
|
|
|
- */
|
|
|
-function tempdir($dir, $prefix = 'tmp', $mode = 0777)
|
|
|
-{
|
|
|
- if (substr($dir, -1) != '/') {
|
|
|
- $dir .= '/';
|
|
|
- }
|
|
|
-
|
|
|
- do {
|
|
|
- $path = $dir.$prefix.mt_rand(0, 9999999);
|
|
|
- } while (!mkdir($path, $mode));
|
|
|
-
|
|
|
- return $path;
|
|
|
-}
|
|
|
-
|
|
|
/**
|
|
|
* Unzip the exercise in the temp folder
|
|
|
* @param string The path of the temporary directory where the exercise was uploaded and unzipped
|
|
@@ -76,9 +57,9 @@ function import_exercise($file)
|
|
|
global $record_item_body;
|
|
|
// used to specify the question directory where files could be found in relation in any question
|
|
|
global $questionTempDir;
|
|
|
+ global $resourcesLinks;
|
|
|
|
|
|
- $archive_path = api_get_path(SYS_ARCHIVE_PATH) . 'qti2';
|
|
|
- $baseWorkDir = $archive_path;
|
|
|
+ $baseWorkDir = api_get_path(SYS_ARCHIVE_PATH) . 'qti2';
|
|
|
|
|
|
if (!is_dir($baseWorkDir)) {
|
|
|
mkdir($baseWorkDir, api_get_permissions_for_new_directories(), true);
|
|
@@ -110,32 +91,50 @@ function import_exercise($file)
|
|
|
}
|
|
|
|
|
|
// find the different manifests for each question and parse them.
|
|
|
-
|
|
|
$exerciseHandle = opendir($baseWorkDir);
|
|
|
//$question_number = 0;
|
|
|
$file_found = false;
|
|
|
$operation = false;
|
|
|
$result = false;
|
|
|
$filePath = null;
|
|
|
+ $resourcesLinks = array();
|
|
|
|
|
|
- // parse every subdirectory to search xml question files
|
|
|
+ // parse every subdirectory to search xml question files and other assets to be imported
|
|
|
+ // The assets-related code is a bit fragile as it has to deal with files renamed by Chamilo and it only works if
|
|
|
+ // the imsmanifest.xml file is read.
|
|
|
while (false !== ($file = readdir($exerciseHandle))) {
|
|
|
if (is_dir($baseWorkDir . '/' . $file) && $file != "." && $file != "..") {
|
|
|
// Find each manifest for each question repository found
|
|
|
$questionHandle = opendir($baseWorkDir . '/' . $file);
|
|
|
+ // Only analyse one level of subdirectory - no recursivity here
|
|
|
while (false !== ($questionFile = readdir($questionHandle))) {
|
|
|
if (preg_match('/.xml$/i', $questionFile)) {
|
|
|
- $result = parse_file($baseWorkDir, $file, $questionFile);
|
|
|
- $filePath = $baseWorkDir . $file;
|
|
|
- $file_found = true;
|
|
|
+ $isQti = isQtiQuestionBank($baseWorkDir . '/' . $file . '/' . $questionFile);
|
|
|
+ if ($isQti) {
|
|
|
+ $result = qti_parse_file($baseWorkDir, $file, $questionFile);
|
|
|
+ $filePath = $baseWorkDir . $file;
|
|
|
+ $file_found = true;
|
|
|
+ } else {
|
|
|
+ $isManifest = isQtiManifest($baseWorkDir . '/' . $file . '/' . $questionFile);
|
|
|
+ if ($isManifest) {
|
|
|
+ $resourcesLinks = qtiProcessManifest($baseWorkDir . '/' . $file . '/' . $questionFile);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
} elseif (preg_match('/.xml$/i', $file)) {
|
|
|
+ $isQti = isQtiQuestionBank($baseWorkDir . '/' . $file);
|
|
|
+ if ($isQti) {
|
|
|
+ $result = qti_parse_file($baseWorkDir, '', $file);
|
|
|
+ $filePath = $baseWorkDir . '/' . $file;
|
|
|
+ $file_found = true;
|
|
|
+ } else {
|
|
|
+ $isManifest = isQtiManifest($baseWorkDir . '/' . $file);
|
|
|
+ if ($isManifest) {
|
|
|
+ $resourcesLinks = qtiProcessManifest($baseWorkDir . '/' . $file);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // Else ignore file
|
|
|
- $result = parse_file($baseWorkDir, '', $file);
|
|
|
- $filePath = $baseWorkDir . '/' . $file;
|
|
|
- $file_found = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -234,7 +233,7 @@ function formatText($text)
|
|
|
* @param string $questionFile
|
|
|
* @return bool
|
|
|
*/
|
|
|
-function parse_file($exercisePath, $file, $questionFile)
|
|
|
+function qti_parse_file($exercisePath, $file, $questionFile)
|
|
|
{
|
|
|
global $non_HTML_tag_to_avoid;
|
|
|
global $record_item_body;
|
|
@@ -252,7 +251,8 @@ function parse_file($exercisePath, $file, $questionFile)
|
|
|
}
|
|
|
|
|
|
//parse XML question file
|
|
|
- $data = str_replace(array('<p>', '</p>', '<front>', '</front>'), '', $data);
|
|
|
+ //$data = str_replace(array('<p>', '</p>', '<front>', '</front>'), '', $data);
|
|
|
+ $data = stripGivenTags($data, array('p', 'front'));
|
|
|
$qtiVersion = array();
|
|
|
$match = preg_match('/ims_qtiasiv(\d)p(\d)/', $data, $qtiVersion);
|
|
|
$qtiMainVersion = 2; //by default, assume QTI version 2
|
|
@@ -292,7 +292,14 @@ function parse_file($exercisePath, $file, $questionFile)
|
|
|
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
|
|
|
- Display:: display_error_message(get_lang('Error reading XML file'));
|
|
|
+ $error = xml_get_error_code();
|
|
|
+ Display::addFlash(
|
|
|
+ Display::return_message(
|
|
|
+ get_lang('Error reading XML file') . sprintf('[%d:%d]', xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser)),
|
|
|
+ 'error'
|
|
|
+ )
|
|
|
+ );
|
|
|
+
|
|
|
return false;
|
|
|
}
|
|
|
|
|
@@ -310,6 +317,7 @@ function parse_file($exercisePath, $file, $questionFile)
|
|
|
'error'
|
|
|
)
|
|
|
);
|
|
|
+
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
@@ -517,6 +525,7 @@ function elementDataQti2($parser, $data)
|
|
|
global $non_HTML_tag_to_avoid;
|
|
|
global $current_inlinechoice_id;
|
|
|
global $cardinality;
|
|
|
+ global $resourcesLinks;
|
|
|
|
|
|
$current_element = end($element_pile);
|
|
|
if (sizeof($element_pile) >= 2) {
|
|
@@ -564,6 +573,13 @@ function elementDataQti2($parser, $data)
|
|
|
}
|
|
|
break;
|
|
|
case 'ITEMBODY':
|
|
|
+ // Replace relative links by links to the documents in the course
|
|
|
+ // $resourcesLinks is only defined by qtiProcessManifest()
|
|
|
+ if (isset($resourcesLinks) && isset($resourcesLinks['manifest']) && isset($resourcesLinks['web'])) {
|
|
|
+ foreach ($resourcesLinks['manifest'] as $key => $value) {
|
|
|
+ $data = preg_replace('|' . $value . '|', $resourcesLinks['web'][$key], $data);
|
|
|
+ }
|
|
|
+ }
|
|
|
$current_question_item_body .= $data;
|
|
|
break;
|
|
|
case 'INLINECHOICE':
|
|
@@ -667,14 +683,9 @@ function startElementQti1($parser, $name, $attributes)
|
|
|
$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
|
|
@@ -719,7 +730,6 @@ function startElementQti1($parser, $name, $attributes)
|
|
|
}
|
|
|
break;
|
|
|
case 'IMG':
|
|
|
- //$exercise_info['question'][$current_question_ident]['attached_file_url'] = $attributes['SRC'];
|
|
|
break;
|
|
|
case 'MATTEXT':
|
|
|
if ($parent_element == 'MATERIAL') {
|
|
@@ -748,6 +758,7 @@ function endElementQti1($parser, $name, $attributes)
|
|
|
global $cardinality;
|
|
|
global $lastLabelFieldName;
|
|
|
global $lastLabelFieldValue;
|
|
|
+ global $resourcesLinks;
|
|
|
|
|
|
$current_element = end($element_pile);
|
|
|
if (sizeof($element_pile) >= 2) {
|
|
@@ -775,7 +786,9 @@ function endElementQti1($parser, $name, $attributes)
|
|
|
switch ($name) {
|
|
|
case 'MATTEXT':
|
|
|
if ($parent_element == 'MATERIAL') {
|
|
|
- if ($grand_parent_element == 'PRESENTATION') {
|
|
|
+ // For some reason an item in a hierarchy <item><presentation><material><mattext> doesn't seem to
|
|
|
+ // catch the grandfather 'presentation', so we check for 'item' as a patch (great-grand-father)
|
|
|
+ if ($grand_parent_element == 'PRESENTATION' OR $grand_parent_element == 'ITEM') {
|
|
|
$exercise_info['question'][$current_question_ident]['title'] = $current_question_item_body;
|
|
|
$current_question_item_body = '';
|
|
|
} elseif ($grand_parent_element == 'RESPONSE_LABEL') {
|
|
@@ -828,6 +841,7 @@ function elementDataQti1($parser, $data)
|
|
|
global $cardinality;
|
|
|
global $lastLabelFieldName;
|
|
|
global $lastLabelFieldValue;
|
|
|
+ global $resourcesLinks;
|
|
|
|
|
|
$current_element = end($element_pile);
|
|
|
if (sizeof($element_pile) >= 2) {
|
|
@@ -879,6 +893,13 @@ function elementDataQti1($parser, $data)
|
|
|
}
|
|
|
break;
|
|
|
case 'MATTEXT':
|
|
|
+ // Replace relative links by links to the documents in the course
|
|
|
+ // $resourcesLinks is only defined by qtiProcessManifest()
|
|
|
+ if (isset($resourcesLinks) && isset($resourcesLinks['manifest']) && isset($resourcesLinks['web'])) {
|
|
|
+ foreach ($resourcesLinks['manifest'] as $key=>$value) {
|
|
|
+ $data = preg_replace('|' . $value . '|', $resourcesLinks['web'][$key], $data);
|
|
|
+ }
|
|
|
+ }
|
|
|
if (!empty($current_question_item_body)) {
|
|
|
$current_question_item_body .= $data;
|
|
|
} else {
|
|
@@ -900,3 +921,90 @@ function elementDataQti1($parser, $data)
|
|
|
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Check if a given file is an IMS/QTI question bank file
|
|
|
+ * @param string $filePath The absolute filepath
|
|
|
+ * @return bool Whether it is an IMS/QTI question bank or not
|
|
|
+ */
|
|
|
+function isQtiQuestionBank($filePath)
|
|
|
+{
|
|
|
+ $data = file_get_contents($filePath);
|
|
|
+ if (!empty($data)) {
|
|
|
+ $match = preg_match('/ims_qtiasiv(\d)p(\d)/', $data);
|
|
|
+ if ($match) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Check if a given file is an IMS/QTI manifest file (listing of extra files)
|
|
|
+ * @param string $filePath The absolute filepath
|
|
|
+ * @return bool Whether it is an IMS/QTI manifest file or not
|
|
|
+ */
|
|
|
+function isQtiManifest($filePath)
|
|
|
+{
|
|
|
+ $data = file_get_contents($filePath);
|
|
|
+ if (!empty($data)) {
|
|
|
+ $match = preg_match('/imsccv(\d)p(\d)/', $data);
|
|
|
+ if ($match) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Processes an IMS/QTI manifest file: store links to new files to be able to transform them into the questions text
|
|
|
+ * @param string $filePath The absolute filepath
|
|
|
+ * @param array $links List of filepaths changes
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+function qtiProcessManifest($filePath)
|
|
|
+{
|
|
|
+ $xml = simplexml_load_file($filePath);
|
|
|
+ $course = api_get_course_info();
|
|
|
+ $sessionId = api_get_session_id();
|
|
|
+ $courseDir = $course['path'];
|
|
|
+ $sysPath = api_get_path(SYS_COURSE_PATH);
|
|
|
+ $exercisesSysPath = $sysPath . $courseDir . '/document/';
|
|
|
+ $webPath = api_get_path(WEB_CODE_PATH);
|
|
|
+ $exercisesWebPath = $webPath . 'document/document.php?' . api_get_cidreq() . '&action=download&id=';
|
|
|
+ $links = array(
|
|
|
+ 'manifest' => array(),
|
|
|
+ 'system' => array(),
|
|
|
+ 'web' => array(),
|
|
|
+ );
|
|
|
+ $tableDocuments = Database::get_course_table(TABLE_DOCUMENT);
|
|
|
+ $countResources = count($xml->resources->resource->file);
|
|
|
+ for ($i=0; $i < $countResources; $i++) {
|
|
|
+ $file = $xml->resources->resource->file[$i];
|
|
|
+ $href = '';
|
|
|
+ foreach ($file->attributes() as $key => $value) {
|
|
|
+ if ($key == 'href') {
|
|
|
+ if (substr($value, -3, 3) != 'xml') {
|
|
|
+ $href = $value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!empty($href)) {
|
|
|
+ $links['manifest'][] = (string) $href;
|
|
|
+ $links['system'][] = $exercisesSysPath . strtolower($href);
|
|
|
+ $specialHref = Database::escape_string(preg_replace('/_/', '-', strtolower($href)));
|
|
|
+ $specialHref = preg_replace('/(-){2,8}/', '-', $specialHref);
|
|
|
+
|
|
|
+ $sql = "SELECT iid FROM " . $tableDocuments . " WHERE c_id = " . $course['real_id'] . " AND session_id = $sessionId AND path = '/" . $specialHref . "'";
|
|
|
+ $result = Database::query($sql);
|
|
|
+ $documentId = 0;
|
|
|
+ while ($row = Database::fetch_assoc($result)) {
|
|
|
+ $documentId = $row['iid'];
|
|
|
+ }
|
|
|
+ $links['web'][] = $exercisesWebPath . $documentId;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return $links;
|
|
|
+}
|