pull/2487/head
Yannick Warnier 9 years ago
commit bee2c0ca06
  1. 1
      main/exercise/export/aiken/aiken_classes.php
  2. 192
      main/exercise/export/exercise_import.inc.php
  3. 9
      main/exercise/export/qti2/qti2_export.php
  4. 3
      main/exercise/qti2.php
  5. 4
      main/inc/ajax/work.ajax.php
  6. 16
      main/inc/lib/api.lib.php
  7. 9
      main/inc/lib/formvalidator/FormValidator.class.php
  8. 1
      main/inc/lib/system/session.class.php
  9. 6
      main/template/default/learnpath/view.tpl
  10. 46
      main/work/work.lib.php
  11. 12
      tests/main/exercice/export/exercise_import.inc.test.php

@ -13,6 +13,7 @@ if (!function_exists('mime_content_type')) {
/**
* @param string $filename
* @return string
*/
function mime_content_type($filename) {
return DocumentManager::file_get_mime_type((string)$filename);

@ -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;
}

@ -34,7 +34,7 @@ class ImsAssessmentItem
*
* @param Ims2Question $question Ims2Question object we want to export.
*/
function ImsAssessmentItem($question)
function __construct($question)
{
$this->question = $question;
$this->answer = $this->question->setAnswer();
@ -155,9 +155,10 @@ class ImsSection
/**
* Constructor.
* @param Exercise $exe The Exercise instance to export
* @return ImsSection
* @author Amand Tihon <amand@alrj.org>
*/
function ImsSection($exe)
function __construct($exe)
{
$this->exercise = $exe;
}
@ -302,9 +303,10 @@ class ImsItem
* Constructor.
*
* @param $question The Question object we want to export.
* @return ImsItem
* @author Anamd Tihon
*/
function ImsItem($question)
function __construct($question)
{
$this->question = $question;
$this->answer = $question->answer;
@ -435,6 +437,7 @@ function export_exercise_to_qti($exerciseId, $standalone = true)
*
* @param int $questionId
* @param bool $standalone (ie including XML tag, DTD declaration, etc)
* @return string
*/
function export_question_qti($questionId, $standalone = true)
{

@ -26,7 +26,7 @@ $interbreadcrumb[]= array (
$is_allowedToEdit = api_is_allowed_to_edit(null, true);
/**
* This function displays the form for import of the zip file with qti2
* This function displays the form to import the zip file with qti2
*/
function ch_qti2_display_form()
{
@ -52,6 +52,7 @@ function ch_qti2_display_form()
/**
* This function will import the zip file with the respective qti2
* @param array $array_file ($_FILES)
* @return string|array
*/
function ch_qti2_import_file($array_file)
{

@ -41,7 +41,7 @@ switch ($action) {
'title' => $file['name'],
'description' => ''
];
$result = processWorkForm($workInfo, $values, $courseInfo, $sessionId, 0, $userId, $file);
$result = processWorkForm($workInfo, $values, $courseInfo, $sessionId, 0, $userId, $file, true);
$json = array();
if (!empty($result) && is_array($result) && empty($result['error'])) {
@ -60,7 +60,7 @@ switch ($action) {
);
} else {
$json['url'] = '';
$json['error'] = get_lang('Error');
$json['error'] = isset($result['error']) ? $result['error'] : get_lang('Error');
}
$resultList[] = $json;
}

@ -8029,3 +8029,19 @@ function api_protect_limit_for_session_admin()
function api_is_student_view_active() {
return (isset($_SESSION['studentview']) && $_SESSION['studentview'] == "studentview");
}
/**
* Like strip_tags(), but leaves an additional space and removes only the given tags
* @param string $string
* @param array $tags Tags to be removed
* @return string The original string without the given tags
*/
function stripGivenTags($string, $tags) {
foreach ($tags as $tag) {
$string2 = preg_replace('#</' . $tag . '[^>]*>#i', ' ', $string);
if ($string2 != $string) {
$string = preg_replace('/<' . $tag . '[^>]*>/i', ' ', $string2);
}
}
return $string;
}

@ -1,6 +1,8 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* Class FormValidator
* create/manipulate/validate user input.
@ -1442,6 +1444,9 @@ EOT;
node
.prepend('<br>')
.prepend(file.preview);
node
.append('<br>')
.append($('<span class=\"text-success\"/>').text('" . get_lang('UplUploadSucceeded') . "'));
}
if (file.error) {
node
@ -1460,7 +1465,6 @@ EOT;
progress + '%'
);
}).on('fileuploaddone', function (e, data) {
$.each(data.result.files, function (index, file) {
if (file.url) {
var link = $('<a>')
@ -1477,7 +1481,8 @@ EOT;
});
}).on('fileuploadfail', function (e, data) {
$.each(data.files, function (index) {
var error = $('<span class=\"text-danger\"/>').text('".get_lang('UploadError')."');
var failedMessage = '" . get_lang('UplUploadFailed') . "';
var error = $('<span class=\"text-danger\"/>').text(failedMessage);
$(data.context.children()[index])
.append('<br>')
.append(error);

@ -39,6 +39,7 @@ class Session implements \ArrayAccess
* Returns true if session has variable set up, false otherwise.
*
* @param string $variable
* @return mixed value
*/
static function has($variable)
{

@ -135,9 +135,9 @@
<div role="tabpanel" class="tab-pane active" id="lp-view-content">
<div id="wrapper-iframe" style="width:100%; height:100%">
{% if lp_mode == 'fullscreen' %}
<iframe id="content_id_blank" name="content_name_blank" style="width:100%; height:100%" src="blank.php" border="0" frameborder="0" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>
<iframe id="content_id_blank" name="content_name_blank" src="blank.php" style="width:100%; height:100%" border="0" frameborder="0" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>
{% else %}
<iframe id="content_id" name="content_name" style="width:100%; height:100%" src="{{ iframe_src }}" border="0" frameborder="0" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>
<iframe id="content_id" name="content_name" src="{{ iframe_src }}" style="width:100%; height:100%" border="0" frameborder="0" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>
{% endif %}
</div>
</div>
@ -171,7 +171,7 @@
'-webkit-overflow-scrolling': 'touch'
});
}
{% if lp_mode == 'embedframe' %}
//$('#learning_path_main').addClass('lp-view-collapsed');
$('#lp-view-expand-button, #lp-view-expand-toggle').on('click', function (e) {

@ -1370,7 +1370,7 @@ function getWorkListStudent(
$lastWork = getLastWorkStudentFromParentByUser($userId, $work['id'], $courseInfo);
if (!empty($lastWork)) {
$work['last_upload'] = Display::label($lastWork['qualification'], 'warning').' - ';
$work['last_upload'] = (!empty($lastWork['qualification'])) ? Display::label($lastWork['qualification'], 'warning').' - ' : '';
$work['last_upload'] .= api_get_local_time($lastWork['sent_date']);
}
@ -3509,6 +3509,23 @@ function sendAlertToUsers($workId, $courseInfo, $session_id)
}
}
/**
* Check if the current uploaded work filename already exists in the current assement
*
* @param $filename
* @param $workId
* @return mixed
*/
function checkExistingWorkFileName($filename, $workId)
{
$work_table = Database :: get_course_table(TABLE_STUDENT_PUBLICATION);
$filename = Database::escape_string($filename);
$sql = "SELECT title FROM $work_table
WHERE parent_id = $workId AND title = '$filename'";
$result = Database::query($sql);
return Database::fetch_assoc($result);
}
/**
* @param array $workInfo
* @param array $values
@ -3517,10 +3534,11 @@ function sendAlertToUsers($workId, $courseInfo, $session_id)
* @param int $groupId
* @param int $userId
* @param array $file
* @param bool $checkDuplicated
*
* @return null|string
*/
function processWorkForm($workInfo, $values, $courseInfo, $sessionId, $groupId, $userId, $file = [])
function processWorkForm($workInfo, $values, $courseInfo, $sessionId, $groupId, $userId, $file = [], $checkDuplicated = false)
{
$work_table = Database :: get_course_table(TABLE_STUDENT_PUBLICATION);
@ -3537,12 +3555,20 @@ function processWorkForm($workInfo, $values, $courseInfo, $sessionId, $groupId,
$filename = null;
$url = null;
$filesize = null;
$workData = [];
if ($values['contains_file']) {
$result = uploadWork($workInfo, $courseInfo, false, [], $file);
if (!$result) {
$saveWork = false;
if ($checkDuplicated) {
if (checkExistingWorkFileName($file['name'], $workInfo['id'])) {
$saveWork = false;
$workData['error'] = get_lang('YouAlreadySentThisFile');
} else {
$result = uploadWork($workInfo, $courseInfo, false, [], $file);
}
} else {
$result = uploadWork($workInfo, $courseInfo, false, [], $file);
}
if (isset($result['error'])) {
$message = $result['error'];
Display::addFlash($message);
@ -3559,12 +3585,14 @@ function processWorkForm($workInfo, $values, $courseInfo, $sessionId, $groupId,
$title = isset($result['title']) && !empty($result['title']) ? $result['title'] : get_lang('Untitled');
}
$filesize = isset($result['filesize']) ? $result['filesize'] : null;
$url = $result['url'];
$url = isset($result['url']) ? $result['url'] : null;
}
if (empty($title)) {
$title = get_lang('Untitled');
}
if (empty($title)) {
$title = get_lang('Untitled');
}
if ($saveWork) {
$active = '1';
$params = [
'c_id' => $courseId,

@ -67,7 +67,7 @@ class TestExerciseImport extends UnitTestCase {
$file = '';
$exercisePath = '';
$questionFile = '';
$res = parse_file($exercisePath, $file, $questionFile);
$res = qti_parse_file($exercisePath, $file, $questionFile);
$this->assertTrue(is_array($res));
if(!is_null){
$this->assertTrue($res);
@ -86,15 +86,5 @@ class TestExerciseImport extends UnitTestCase {
}
//var_dump($res);
}
function testtempdir() {
$dir = '/tmp';
$res = tempdir($dir, $prefix='tmp', $mode=0777);
$this->assertFalse(is_array($res));
if(!is_null){
$this->assertTrue(is_string($res));
}
//var_dump($res);
}
}
?>

Loading…
Cancel
Save