View source
<?php
define('QUIZ_QUESTIONS_UPLOAD_ARCHIVE_PATH', 'quiz_questions_import_archive');
module_load_include('php', 'quiz', 'includes/moodle_support');
function questions_import_get_sample_file_link() {
$path = drupal_get_path('module', 'questions_import') . '/examples/';
return array(
'!csv' => l(t('CSV'), $path . 'csv_example.csv'),
'!aiken' => l(t('AIKEN'), $path . 'aiken_example.txt'),
'!gift' => l(t('GIFT'), $path . 'gift_example.txt'),
'!learnwise' => l(t('LEARNWISE'), $path . 'learnwise_example.xml'),
'!qti_v1p2' => l(t('QTI 1.2'), $path . 'qti_v1p2_sample.xml'),
);
}
function questions_import_form() {
$form['#attributes'] = array(
'enctype' => 'multipart/form-data',
);
$form['quiz_destination'] = array(
'#type' => 'fieldset',
'#title' => t('Questions destination'),
'#collapse' => FALSE,
);
$form['quiz_destination']['destination_type'] = array(
'#type' => 'select',
'#title' => t('Import destination'),
'#options' => array(
'new_qcollection' => t('New item collection'),
'new_quiz' => t('New quiz'),
'existing_quiz' => t('Existing quiz'),
),
'#required' => TRUE,
);
$form['quiz_destination']['destination_title'] = array(
'#type' => 'textfield',
'#title' => t('Name of new node'),
'#default_value' => t('<unnamed>'),
'#description' => t('Name for the new quiz or item collection'),
'#size' => 30,
'#required' => TRUE,
);
$options = quiz_get_all_quiz_title();
$form['quiz_destination']['quiz_node'] = array(
'#type' => 'select',
'#title' => t('Existing quiz'),
'#options' => $options,
'#description' => count($options) ? t('Select the quiz node for which you want to add questions') : t('No quiz contents where found. To !create_a_quiz go to Content Management -> Create Content -> Quiz.', array(
'!create_a_quiz' => l(t('create a quiz'), 'node/add/quiz'),
)),
);
$form['quiz_import'] = array(
'#type' => 'fieldset',
'#title' => t('Questions source'),
'#description' => t('Questions import module allows the user with role "quiz author" to create a bulk of questions from portable text file (like CSV, XML etc) where questions are defined in some predefined format.'),
'#collapse' => TRUE,
'#collapsed' => FALSE,
);
$form['quiz_import']['import_type'] = array(
'#type' => 'select',
'#title' => t('Import type'),
'#options' => questions_import_type(),
'#description' => t('Select the import type (sample files !csv, !aiken, !gift, !learnwise, !qti_v1p2)', questions_import_get_sample_file_link()),
'#required' => TRUE,
);
$form['quiz_import']['field_separator'] = array(
'#type' => 'textfield',
'#title' => t('CSV Field Separator'),
'#default_value' => t(','),
'#description' => t('Special character used to separator the fields usually , : or ; '),
'#size' => 1,
);
$form['quiz_import']['upload'] = array(
'#type' => 'file',
'#title' => t('Upload'),
'#size' => 30,
'#description' => t('Upload the file that has quiz questions'),
);
$form['advanced'] = array(
'#type' => 'fieldset',
'#title' => t('Advanced'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$formats = filter_formats();
foreach ($formats as $info) {
$format_options[$info->format] = $info->name;
}
$form['advanced']['input_format'] = array(
'#type' => 'select',
'#title' => t('Input format'),
'#options' => $format_options,
'#required' => TRUE,
);
$form['advanced']['ignore_filename_extensions'] = array(
'#type' => 'checkbox',
'#title' => t('Ignore filename extension'),
'#default_value' => FALSE,
'#description' => t('Normally this form checks that the suffix of the uploaded file. This allows you to skip that.'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Import'),
);
$form['#validate'][] = 'questions_import_form_validate';
$form['#submit'][] = 'questions_import_form_submit';
return $form;
}
function questions_import_type() {
$type = array(
'native_csv' => t('Comma Separated Values (by Drupal)'),
'native_aiken' => t('Aiken (by Drupal)'),
'native_qti12' => t('QTI 1.2 (by Drupal)'),
'moodle_aiken' => t('Aiken (by Moodle)'),
'moodle_gift' => t('GIFT (by Moodle)'),
'moodle_learnwise' => t('Learnwise (by Moodle)'),
'moodle_webct' => t('WebCT (by Moodle)'),
);
return $type;
}
function questions_import_form_validate($form, &$form_state) {
$allowed_extensions = 'csv txt xml jcl jqz';
$allowed_size = file_upload_max_size();
$field_separator = $form_state['values']['field_separator'];
$import_type_info = explode('_', $form_state['values']['import_type']);
$import_engine = $import_type_info[0];
$import_format = $import_type_info[1];
$import_archive_path = file_directory_path() . '/' . QUIZ_QUESTIONS_UPLOAD_ARCHIVE_PATH;
file_check_directory($import_archive_path, FILE_CREATE_DIRECTORY);
$file = file_save_upload('upload', array(), $import_archive_path);
if (!$file) {
form_set_error('upload', 'You must select a valid file to upload.');
}
else {
$validate_extensions = !$form_state['values']['ignore_filename_extensions'];
if ($validate_extensions) {
$error_msg = question_import_validate_extensions($file, $allowed_extensions);
}
if ($error_msg != '') {
form_set_error('upload', $error_msg);
}
$error_msg = question_import_validate_size($file, $allowed_size);
if ($error_msg != '') {
form_set_error('upload', $error_msg);
}
switch ($import_engine) {
case 'native':
$validator_function_name = "questions_import_validate_{$import_format}";
$error_msg = $validator_function_name($file, $field_separator);
break;
case 'moodle':
break;
}
if ($error_msg != '') {
form_set_error('upload', $error_msg);
}
else {
$success = file_set_status($file, FILE_STATUS_PERMANENT);
if ($success) {
$form_state['values']['validated_file'] = $file;
}
else {
form_set_error('upload', "Failed to store file permanently");
}
}
}
}
function questions_import_validate_qti12($file, $field_separator) {
$error_msg = '';
$row = 0;
$lines = file($file->filepath);
if (empty($lines)) {
form_set_error('xmlfile', 'File could not be uploaded. Please try again.');
}
}
function questions_import_validate_aiken($file, $separator) {
}
function questions_import_validate_csv($file, $separator) {
$error_msg = '';
$row = 0;
$lines = file($file->filepath);
if (empty($lines)) {
return '<p>' . t('No lines were found in @filename.', array(
'@filename' => $file->filename,
)) . '</p>';
}
foreach ($lines as $line) {
$line = check_plain(trim($line));
if (!empty($line)) {
++$row;
$fields = explode($separator, $line);
switch ($field[0]) {
case 'multichoice':
$error_msg .= count(fields) < 4 ? '<p>' . t('line : ') . $row . ' ' . $line . ' </p>' : '';
break;
case 'true_false':
$error_msg .= '';
}
}
}
$error_msg .= !empty($error_msg) ? '<p>' . t('CSV Import Failed. These lines were found to have an invalid number of fields in @filename.', array(
'@filename' => $file->filename,
)) . '</p>' : '';
return $error_msg;
}
function _get_destination_node($form_state) {
global $user;
$destination_type = $form_state['values']['destination_type'];
if ($destination_type == 'existing_quiz') {
$quiz_nid = $form_state['values']['quiz_node'];
return node_load($quiz_nid);
}
else {
$title = $form_state['values']['destination_title'];
list($tmp, $new_type) = explode('_', $destination_type);
$node = new stdClass();
$node->type = $new_type;
$node->title = $title;
$node->uid = $user->uid;
$node->format = $form_state['values']['input_format'];
$node->status = 1;
$node->comment = 0;
$node->log = 'Created by Quiz questions importer.';
node_save($node);
return $node;
}
}
function questions_import_form_submit(&$form, &$form_state) {
$time = 0;
$op = '';
$destination_node = _get_destination_node($form_state);
$import_type_info = explode('_', $form_state['values']['import_type']);
$import_engine = $import_type_info[0];
$import_format = $import_type_info[1];
$question_type = $form_state['values']['question_type'];
$start = microtime(TRUE);
$status = array();
$date = date('YmdHis');
$fid = $form_state['values']['validated_file']->fid;
db_query("INSERT INTO {quiz_questions_import_record} (file_id, datetime, status_array, importer) VALUES (%d, '%s', '%s', '%s')", $fid, $date, serialize($status), $form_state['values']['import_type']);
$import_id = db_last_insert_id('quiz_questions_import_record', 'import_id');
switch ($import_engine) {
case 'native':
$file = file_save_upload('upload');
$import_function_name = "questions_import_submit_{$import_format}";
$count = $import_function_name($destination_node, $form, $form_state, $import_id);
break;
case 'moodle':
$count = questions_import_submit_moodle_format($destination_node, $import_format, $form, $form_state, $import_id);
break;
}
$end = microtime(TRUE);
$time = substr($end - $start, 0, 6);
if ($count) {
drupal_set_message(t('@count questions were imported successfully in @time seconds.', array(
'@count' => $count,
'@time' => $time,
)));
$url = $form_state['values']['destination_type'] == 'new_quiz' ? "node/{$destination_node->nid}/edit" : "node/{$destination_node->nid}";
drupal_goto($url);
}
return $count;
}
function questions_import_submit_moodle_format($destination_node, $format, $form, $form_state, $import_id) {
$file = $form_state['values']['validated_file'];
module_load_include('php', 'quiz', "includes/moodle/question/format/{$format}/format");
$classname = "qformat_{$format}";
$fHandler = new $classname();
assert($fHandler
->provide_import());
$lines = file($file->filepath);
$moodle_questions = $fHandler
->readquestions($lines);
$import_count = 0;
foreach ($moodle_questions as $mq) {
questions_import_moodle_create_node($destination_node, $mq, $form_state, $row, $import_id);
++$import_count;
}
return $import_count;
}
function questions_import_moodle_create_node($destination_node, $mq, $form_state, $row, $import_id) {
$qmap_drupal_to_moodle = array(
'long_answer' => 'essay',
'short_answer' => 'shortanswer',
'choice' => 'multichoice',
'true_false' => 'truefalse',
'quiz_directions' => 'description',
'matching' => 'match',
);
$qmap_moodle_to_drupal = array_flip($qmap_drupal_to_moodle);
global $user;
$node = new stdClass();
$node->type = $qmap_moodle_to_drupal[$mq->qtype];
$node->title = $mq->name;
$node->teaser = $node->body = stripslashes_safe($mq->questiontext);
$node->uid = $user->uid;
$node->format = $form_state['values']['input_format'];
$node->log = 'Imported with Moodle importer.';
$node->quiz_id = $destination_node->nid;
$node->quiz_vid = $destination_node->vid;
switch (strtolower($mq->qtype)) {
case 'match':
$node->match = array();
foreach ($mq->subquestions as $index => $sub_question) {
$sub_answer = $mq->subanswers[$index];
$matching_pair = array(
'question' => $sub_question,
'answer' => $sub_answer,
);
$node->match[] = $matching_pair;
}
break;
case 'shortanswer':
$node->correct_answer = $mq->answer[1];
$node->correct_answer_evaluation = ShortAnswerQuestion::ANSWER_INSENSITIVE_MATCH;
$node->maximum_score = $mq->defaultgrade;
break;
case 'multichoice':
$node->alternatives = array();
$node->choice_multi = !$mq->single;
foreach ($mq->answer as $index => $answer) {
$node->alternatives[] = array(
'answer' => $answer,
'answer_format' => $form_state['values']['input_format'],
'feedback_if_chosen' => $mq->feedback[$index],
'feedback_if_chosen_format' => $mq->feedback[$index],
'feedback_if_not_chosen' => '',
'feedback_if_not_chosen_format' => $mq->feedback[$index],
'score_if_chosen' => $mq->fraction[$index],
'score_if_not_chosen' => 0,
);
}
break;
}
node_save(questions_import_node_save_static_data($node));
db_query("INSERT INTO {quiz_questions_import_items} VALUES (%d, %d, %d)", $import_id, $node->nid, $row);
}
function questions_import_submit_qti12($destination_node, $form, $form_state, $import_id) {
$file = $form_state['values']['validated_file'];
$row = 0;
$qti_items = questions_import_qti_extract_info($file->filepath);
foreach ($qti_items as $item) {
questions_import_qti_create_node($destination_node, $item, $form_state, $row, $import_id);
++$row;
}
return $row;
}
function questions_import_qti_create_node($destination_node, $item, $form_state, $row, $import_id) {
global $user;
$item = (object) $item;
$node = new stdClass();
$node->title = $item->title;
$node->teaser = $node->body = $item->content;
$node->uid = $user->uid;
$node->format = $form_state['values']['input_format'];
$node->status = 1;
$node->log = 'Imported from QTI importer.';
$node->quiz_id = $destination_node->nid;
$node->quiz_vid = $destination_node->vid;
switch (strtolower($item->type)) {
case 'multiple choice':
$node->type = 'multichoice';
$answers = $item->answers;
$node->number_of_answers = count($answers);
$node->answers = array();
foreach ($answers as $answer) {
$node->answers[] = array(
'answer' => $answer['text'],
'feedback' => $answer['feedback'],
'correct' => $answer['is_correct'],
'result_option' => '0',
);
}
break;
}
node_save(questions_import_node_save_static_data($node));
db_query("INSERT INTO {quiz_questions_import_items} VALUES (%d, %d, %d)", $import_id, $node->nid, $row);
}
function questions_import_qti_extract_info($file) {
$items = array();
foreach (qp($file, 'item') as $item) {
$title = $item
->attr('title');
$type = $item
->find('itemmetadata>qmd_itemtype')
->text();
$body = $item
->end()
->find('presentation>material>mattext');
if ($body
->attr('texttype') == 'text/html') {
$bodytext = $body
->text();
if (strpos($bodytext, '<html') === FALSE) {
drupal_set_message('Adding HTML', 'status');
$bodytext = '<html>' . $bodytext . '</html>';
}
$doc = new DOMDocument();
$doc
->loadHTML($bodytext);
$html = qp($doc, 'body');
$contents = $html
->get(0)->childNodes;
$newdoc = qp('<?xml version="1.0"?><div id="qti-question-body"/>');
$i = 0;
while ($node = $contents
->item($i++)) {
$newdoc
->append($node);
}
$out = $newdoc
->html();
}
else {
$out = $body
->text();
}
$new_item = array(
'title' => $title,
'type' => $type,
'content' => $out,
);
if (strtolower($type) == 'multiple choice') {
$answers = array();
$answerstexts = $item
->parent('item')
->find('response_lid>render_choice>response_label>material>mattext');
foreach ($answerstexts as $answertext) {
$text = $answertext
->text();
$index = $answertext
->parent('response_label')
->attr('ident');
$contains_filter = 'resprocessing>respcondition>conditionvar>varequal:contains(' . $index . ')';
$correct = $answertext
->parent('item')
->find($contains_filter)
->parent('respcondition')
->find('setvar')
->text();
if ($correct == 0) {
$feedback = $answertext
->parent('item')
->find('itemfeedback[ident="Wrong Answer"]>material>mattext')
->text();
}
else {
$feedback = 'Correct';
}
$answers[] = array(
'text' => $text,
'index' => $index,
'is_correct' => $correct,
'feedback' => $feedback,
);
}
$new_item['answers'] = $answers;
}
$items[] = $new_item;
}
return $items;
}
function questions_import_submit_aiken($destination_node, $form, $form_state, $import_id) {
global $user;
$row = 0;
$output = '';
$line = array();
$file = $form_state['values']['validated_file'];
$lines = file($file->filepath);
while (!empty($lines)) {
while ($current_line = questions_import_get_next_aiken_field($lines)) {
if (empty($current_line)) {
break;
}
if ($current_line[0] === '#') {
continue;
}
$line[] = $current_line;
}
if (empty($line)) {
continue;
}
$type = questions_import_get_next_aiken_field($line);
if (!in_array($type, quiz_get_questions_type('enabled'))) {
drupal_set_message(t('Unable to import @type type question. You may need to enable @type module.', array(
'@type' => $type,
)), 'error');
$line = array();
continue;
}
$node = new stdClass();
$node->type = $type;
$node->format = $form_state['values']['input_format'];
$node->quiz_id = $destination_node->nid;
$node->quiz_vid = $destination_node->vid;
$node->type = $type;
$node->title = $node->body = $node->teaser = questions_import_get_next_aiken_field($line);
$options = array();
switch ($node->type) {
case 'multichoice':
$options = array();
$answer = array_pop($line);
while (!empty($line)) {
$l = questions_import_get_next_aiken_field($line);
$option = explode($l[1], $l);
$options[trim($option[0])]['choice'] = trim($option[1]);
$feedback = questions_import_get_next_aiken_field($line);
$options[trim($option[0])]['feedback'] = $feedback == 'nil' ? '' : $feedback;
}
$correct = substr(trim($answer), '-1');
$answer = $options[$correct]['choice'];
$line = array();
$node->num_answers = count($options);
$node->answers = array();
foreach ($options as $option) {
$node->answers[] = array(
'correct' => trim($answer) == trim($option['choice']) ? 1 : 0,
'answer' => trim($option['choice']),
'feedback' => trim($option['feedback']),
);
}
break;
case 'true_false':
$node->correct_answer = questions_import_get_next_aiken_field($line) == 'true' ? 1 : 0;
$feedback = questions_import_get_next_aiken_field($line);
$node->feedback = $feedback === 'nil' ? '' : $feedback;
break;
case 'matching':
$node->match = array();
while (!empty($line)) {
$row = explode(',', questions_import_get_next_aiken_field($line));
$node->match[] = array(
'question' => $row[0],
'answer' => $row[1],
'feedback' => $row[2] == 'nil' ? '' : $row[2],
);
}
break;
case 'short_answer':
$evaluation_type = array(
'case sensitive match',
'case insensitive match',
'regular expression match',
'manually score match',
);
$node->correct_answer = questions_import_get_next_aiken_field($line);
$node->maximum_score = (int) questions_import_get_next_aiken_field($line);
$node->correct_answer_evaluation = (int) array_search(questions_import_get_next_aiken_field($line), $evaluation_type);
break;
case 'long_answer':
$node->maximum_score = questions_import_get_next_aiken_field($line);
break;
case 'quiz_directions':
break;
}
node_save(questions_import_node_save_static_data($node));
db_query("INSERT INTO {quiz_questions_import_items} VALUES (%d, %d, %d)", $import_id, $node->nid, $row);
++$row;
}
return $row;
}
function questions_import_get_next_aiken_field(&$row) {
return trim(array_shift($row));
}
function questions_import_submit_csv($destination_node, $form, $form_state, $import_id) {
global $user;
$row = 0;
$output = '';
$file = $form_state['values']['validated_file'];
$separator = $form_state['values']['field_separator'];
$lines = file($file->filepath);
foreach ($lines as $line) {
$line = trim($line);
if (empty($line) || $line[0] === '#') {
continue;
}
$node = new stdClass();
$node->format = $form_state['values']['input_format'];
$node->quiz_id = $destination_node->nid;
$node->quiz_vid = $destination_node->vid;
$line = explode($separator, $line);
$type = questions_import_get_next_csv_field($line);
if (!in_array($type, quiz_get_questions_type('enabled'))) {
drupal_set_message(t('Unable to import @type type question. You may need to enable @type module.', array(
'@type' => $type,
)), 'error');
continue;
}
$node->type = $type;
$question = questions_import_get_next_csv_field($line);
switch ($type) {
case 'multichoice':
$correct_answer = array_pop($line);
$node->num_answers = count($options);
$node->answers = array();
while (!empty($line)) {
$answer = questions_import_get_next_csv_field($line);
$feedback = questions_import_get_next_csv_field($line);
$node->answers[] = array(
'answer' => $answer,
'feedback' => $feedback === 'nil' ? '' : $feedback,
'correct' => trim($answer) == trim($correct_answer) ? 1 : 0,
);
}
break;
case 'true_false':
$node->correct_answer = questions_import_get_next_csv_field($line) == 'true' ? 1 : 0;
$feedback = questions_import_get_next_csv_field($line);
$node->feedback = $feedback === 'nil' ? '' : $feedback;
break;
case 'matching':
$node->match = array();
while (!empty($line)) {
$match = array(
'question' => questions_import_get_next_csv_field($line),
'answer' => questions_import_get_next_csv_field($line),
'feedback' => questions_import_get_next_csv_field($line),
);
$node->match[] = array(
'question' => $match['question'],
'answer' => $match['answer'],
'feedback' => $match['feedback'] ? '' : $match['feedback'],
);
}
break;
case 'long_answer':
$node->maximum_score = questions_import_get_next_csv_field($line);
break;
case 'short_answer':
$evaluation = array(
'case sensitive match',
'case insensitive match',
'regular expression match',
'manually score match',
);
$node->correct_answer = questions_import_get_next_csv_field($line);
$node->maximum_score = questions_import_get_next_csv_field($line);
$node->correct_answer_evaluation = (int) array_search(questions_import_get_next_csv_field($line), $evaluation);
break;
case 'quiz_directions':
break;
}
$node->title = $node->body = $node->teaser = $question;
node_save(questions_import_node_save_static_data($node));
db_query("INSERT INTO {quiz_questions_import_items} VALUES (%d, %d, %d)", $import_id, $node->nid, $row);
++$row;
}
return $row;
}
function questions_import_get_next_csv_field(&$row) {
return trim(array_shift($row));
}
function questions_import_node_save_static_data(&$node) {
global $user;
$node->uid = $user->uid;
$node->name = $user->name;
$node->promote = 0;
$node->sticky = 0;
$node->status = 1;
$node->comment = 0;
$node->moderate = 0;
$node->multiple_answers = 0;
$node->more = 0;
$node->validate = 1;
$node->is_new = 1;
$node->scored_quiz = 1;
$node->revision = 1;
$node->op = t('Save');
$node->preview = t('Preview');
return $node;
}
function question_import_validate_extensions($file, $extensions) {
global $user;
$errors = '';
$regex = '/\\.(' . ereg_replace(' +', '|', preg_quote($extensions)) . ')$/i';
if (!preg_match($regex, $file->filename)) {
$errors = '<p>' . t('Only files with the following extensions are allowed: %files-allowed.', array(
'%files-allowed' => $extensions,
)) . '</p>';
}
return $errors;
}
function question_import_validate_size($file, $file_limit = 0, $user_limit = 0) {
global $user;
$errors = '';
if ($file_limit && $file->filesize > $file_limit) {
$errors = '<p>' . t('The file is %filesize exceeding the maximum file size of %maxsize.', array(
'%filesize' => format_size($file->filesize),
'%maxsize' => format_size($file_limit),
)) . '</p>';
}
$total_size = file_space_used($user->uid) + $file->filesize;
if ($user_limit && $total_size > $user_limit) {
$errors = '<p>' . t('The file is %filesize which would exceed your disk quota of %quota.', array(
'%filesize' => format_size($file->filesize),
'%quota' => format_size($user_limit),
)) . '</p>';
}
return $errors;
}