diff --git a/question/format/clozejr/format.php b/question/format/clozejr/format.php new file mode 100644 index 00000000000..c44cc3bbabe --- /dev/null +++ b/question/format/clozejr/format.php @@ -0,0 +1,470 @@ +libdir/xmlize.php" ); + global $CFG, $langfile; + $langfile = $CFG->dirroot.'/question/format/clozejr/lang/'; + +class qformat_clozejr extends qformat_default { + function provide_import() { + return false; + } + function provide_export() { + return true; + } + + // IMPORT FUNCTIONS START HERE + + function importprocess($lines) { + $questions = $this->readquestions($lines); + // Now process and store the unique cloze question + $question = $questions[0]; + $question->category = $this->category->id; + $question->stamp = make_unique_id_code(); // Set the unique code (not to be changed) + if (!$question->id = insert_record("question", $question)) { + error( get_string('cannotinsert','quiz') ); + } + $this->questionids[] = $question->id; + // Now to save all the answers and type-specific options + global $QTYPES; + $question->course = ''; + $result = $QTYPES[$question->qtype] + ->save_question_options($question); + + if (!empty($result->error)) { + notify($result->error); + return false; + } + + if (!empty($result->notice)) { + notify($result->notice); + return true; + } + // Give the question a unique version stamp determined by question_hash() + set_field('question', 'version', question_hash($question), 'id', $question->id); + + return true; + } + + function import_text( $text ) { + $data = $text[0]['#']; + $data = html_entity_decode( $data ); + return trim( $data ); + } + + function import_answer( $answer ) { + // import answer part of question + + $fraction = $answer['@']['fraction']; + $text = $this->import_text( $answer['#']['text']); + $feedback = $this->import_text( $answer['#']['feedback'][0]['#']['text'] ); + + $ans = null; + $ans->answer = $text; + $ans->fraction = $fraction / 100; + $ans->feedback = $feedback; + + return $ans; + } + + function import_question( $question ) { + // run through the answers + $answers = $question['#']['answer']; + $an = ''; + foreach ($answers as $answer) { + $ans = $this->import_answer( $answer ); + $fraction = $ans->fraction * 100; + $an.="~%".$fraction."%".$ans->answer."#".$ans->feedback; + } + return $an; + } + + function import_numerical( $question ) { + // import numerical question + // get answers array + $answers = $question['#']['answer']; + $an = ''; + foreach ($answers as $answer) { + $fraction = trim($answer['#']['fraction'][0]['#']); + $ans = trim($answer['#'][0]); + $tolerance = trim($answer['#']['tolerance'][0]['#']); + $feedback = addslashes(trim(( $answer['#']['feedback'][0]['#']['text'][0]['#'] ))); + $percent = $fraction * 100; + $an.="~%$percent%".$ans.":".$tolerance."#".$feedback; + } + return $an; + } + + function reverse_strrchr($haystack, $needle) + { + $pos = strrpos($haystack, $needle); + if($pos === false) { + return $haystack; + } + return substr($haystack, 0, $pos + 0).'
'; + } + + function readquestions($lines) { + // this converts xml to big nasty data structure + // the 0 means keep white space as it is (important for markdown format) + // print_r it if you want to see what it looks like! + global $CFG, $langfile; + $xml = xmlize( $lines, 0 ); + // set up array to hold all our questions + $questions = array(); + $subquestions = array(); + $clozequestionname = $xml['QUESTION_CATEGORY']['#']['NAME'][0]['#']; + notify(get_string( 'displayclozequestion','clozejr','',$langfile) ); + + // iterate through questions + $count = 0; + $numberthequestions = false; + foreach ($xml['QUESTION_CATEGORY']['#']['question'] as $question) { + $count++; + $questiontype = $question['@']['type']; + $an = null; + $defaultgrade = $question['#']['DEFAULTGRADE'][0]['#']; + switch($questiontype) { + CASE 'numerical' : + $an = $this->import_numerical( $question ); + break; + CASE 'description' : + break; + CASE 'multichoice': + CASE 'regexp': + CASE 'shortanswer': + $an = addslashes( $this->import_question($question) ); + break; + CASE 'matching' : + CASE 'cloze' : + default: + $an = null; + } + // stick the result in the $questions array + $isnewline = false; + if ($an OR ($questiontype == 'description')) { + //addslashes added JR 29 MAR 2007 + $qtext = addslashes($this->import_text( $question['#']['questiontext'][0]['#']['text'] ) ); + $qtext = ereg_replace("(||\n)", "", $qtext); + // remove all

tags and potential newline \n from question text ... but keeps existing
tags if any + $newline = ''; + $pos = strrpos($qtext, ' with
+ + $qtext_array = explode('***', $qtext,2); + // replace "blank" *** sequences in original question text with question slot + + $name = $this->import_text( $question['#']['name'][0]['#']['text'] ); + if ($count === 1 and substr($name, 2, 1) == '-') { + // only number questions if the very first one is something like '01-' + $numberthequestions = true; + } + $n = "00"; + $nb = substr($name, 0, 2); + // get first 2 letters of question name - if 01, 02, ... 99, then change into 01- ... 99- + if ($nb + 0 != 0) { + $n = $nb.'- '; + } else { + $n = ''; + } + if ($questiontype == 'description') { + $subquestions[] = $n.$qtext.$newline; + } else { + $subquestions[] = $n.$qtext_array[0]."{".$defaultgrade.":".strtoupper($questiontype) + .":".$an."}".$qtext_array[1].$newline; + } + } + } + sort($subquestions); + if ($numberthequestions == false) { + $count = 0; + foreach ($subquestions as $question) { + $subquestions[$count] = substr($question,3); + $count++; + } + } + $subquestions = implode(" ",$subquestions); + $qo = qtype_multianswer_extract_question($subquestions); + $qo->name = $clozequestionname; + // the cloze question is named after the name of the question_category from which the questions were exported + $questions[] = $qo; + //now replace all {#} blanks in question text with correct answwer (if more than one, first met is selected) + $qtext = $qo->questiontext; + $countquestions = 1; + foreach ($qo->options->questions as $question) { + $count = 0; + foreach ($question->answer as $answer) { + if ($question->fraction[$count] == 1) { + $qtext = ereg_replace("\{\#".$countquestions."\}", "{".$answer."}", $qtext); + break; + } + $count++; + } + $countquestions++; + } + echo "

".stripslashes($qtext)."

"; + // print it here rather than in importprocess() + return $questions; + } + + // EXPORT FUNCTIONS START HERE + + function exportprocess() { + /// Exports a given category. There's probably little need to change this + global $CFG, $langfile; + + // create a directory for the exports (if not already existing) + if (! $export_dir = make_upload_directory($this->question_get_export_dir())) { + error( get_string('cannotcreatepath','quiz',$export_dir) ); + } + $path = $CFG->dataroot.'/'.$this->question_get_export_dir(); + + // get the questions (from database) in this category + // only get q's with no parents (no cloze subquestions specifically) + $questions = get_questions_category( $this->category, true ); + notify( get_string('displayquestions', 'clozejr', '', $langfile) ); + + if (!count($questions)) { + notify( get_string('noquestions','quiz') ); + return false; + } + $count = 0; + + // results are first written into string (and then to a file) + // so create/initialize the string here + $expout = ""; + + // iterate through questions + foreach($questions as $question) { + // do not export hidden questions + if (!empty($question->hidden)) { + continue; + } + + // do not export questions of these types + $questiontype = $question->qtype; + $questiontypename = $this->get_qtype($questiontype); + if ($questiontype==RANDOM or $questiontype==MULTIANSWER or $questiontype==MATCH or $questiontype==TRUEFALSE or $questiontype==ESSAY or $questiontype==RANDOMSAMATCH) { + notify( get_string( 'clozetypeunsupported', 'clozejr', '', $langfile) ); + continue; + } + + // export the question displaying message + $count++; + $qtext = stripslashes($question->questiontext); + $qtext = ereg_replace("", "", $qtext); + + echo "

$count. ".$qtext."

"; + $expout .= $this->writequestion( $question ) . "\n"; + } + + // final pre-process on exported data + $expout = $this->presave_process( $expout ); + $result = $this->importprocess ($expout); + return true; + } + + function get_qtype( $type_id ) { + // translates question type code number into actual name + + switch( $type_id ) { + case TRUEFALSE: + $name = 'truefalse'; + break; + case MULTICHOICE: + $name = 'multichoice'; + break; + case SHORTANSWER: + $name = 'shortanswer'; + break; + case 'regexp': + $name = 'regexp'; + break; + case NUMERICAL: + $name = 'numerical'; + break; + case MATCH: + $name = 'matching'; + break; + case DESCRIPTION: + $name = 'description'; + break; + case ESSAY: + $name = 'essay'; + break; + case MULTIANSWER: + $name = 'cloze'; + break; + default: + $name = 'unknown'; + } + return $name; + } + + + function get_single( $id ) { + // translate single value into something sensible + + switch( $id ) { + case 0: + $name = "false"; + break; + case 1: + $name = "true"; + break; + default: + $name = "unknown"; + } + return $name; + } + + function writetext( $raw, $ilev=0, $short=true) { + // generates tags, processing raw text therein + // $ilev is the current indent level + // $short=true sticks it on one line + $indent = str_repeat( " ",$ilev ); + + // encode the text to 'disguise' HTML content + $raw = htmlspecialchars( $raw ); + + if ($short) { + $xml = "$indent$raw\n"; + } + else { + $xml = "$indent\n$raw\n$indent\n"; + } + + return $xml; + } + + function presave_process( $content ) { + // override method to allow us to add xml headers and footers + global $categoryname; + $categoryname = ''; + $categoryid = optional_param('category',0, PARAM_INT); + if (!$categories = get_records_select("question_categories", "id = '{$categoryid}'")) { + error("Could not find any question categories!"); + } + $categories = add_indented_names($categories); + foreach ($categories as $key => $cat) { + $categoryname = $cat->name; + } + notify ("categoryname = $categoryname"); + $categoryid = 1; + $content = "\n" . + "\n" . + "".$categoryid."\n" . + "".$categoryname."\n" . + $content . "\n" . + ""; + return $content; + } + + function exportpostprocess() { + /// Does any post-processing that may be desired + global $langfile, $categoryname; + notify( get_string('donotclick', 'clozejr', $categoryname, $langfile) ); + return true; + } + + function writequestion( $question ) { + // turns question into string + // initial string; + $expout = ""; + $questiontype = $question->qtype; + switch( $questiontype ) { + case MULTICHOICE: + if ($this->get_single($question->options->single) == 'true') { + break; + } + case SHORTANSWER: + case 'regexp': + case DESCRIPTION: + case NUMERICAL: + break; + case MATCH: + case MULTIANSWER: //cloze/embedded + case RANDOMSAMATCH: + case TRUEFALSE: + default: + notify( get_string( 'clozetypeunsupported','clozejr',$questiontype) ); + } + + if ($questiontype == 'unsupported') { + return null; + } + // add comment + $expout .= "\n\n\n"; + $defaultgrade = $question->defaultgrade; + // add opening tag + // generates specific header for Cloze type question + + $nametext = $this->writetext( $question->name ); + $qtformat = ""; + $questiontext = $this->writetext( $question->questiontext ); + $expout .= " \n"; + $expout .= " $nametext\n"; + $expout .= " ".$defaultgrade."\n"; + $expout .= " \n".$questiontext; + $expout .= " \n"; + // output depends on question type + switch($question->qtype) { + case MULTICHOICE: + case SHORTANSWER: + case 'regexp': + foreach($question->options->answers as $answer) { + $percent = $answer->fraction * 100; + $expout .= " \n"; + $expout .= $this->writetext( $answer->answer,4,false ); + $expout .= " \n"; + $expout .= $this->writetext( $answer->feedback,4,false ); + $expout .= " \n"; + $expout .= " \n"; + } + break; + case NUMERICAL: + foreach ($question->options->answers as $answer) { + $tolerance = $answer->tolerance; + $expout .= "\n"; + $expout .= " {$answer->answer}\n"; + $expout .= " $tolerance\n"; + $expout .= " ".$this->writetext( $answer->feedback )."\n"; + $expout .= " {$answer->fraction}\n"; + $expout .= "\n"; + } + + $units = $question->options->units; + if (count($units)) { + $expout .= "\n"; + foreach ($units as $unit) { + $expout .= " \n"; + $expout .= " {$unit->multiplier}\n"; + $expout .= " {$unit->unit}\n"; + $expout .= " \n"; + } + $expout .= "\n"; + } + break; + case DESCRIPTION: + // nothing more to do for this type + break; + default: + $expout .= "\n"; + } + + // close the question tag + $expout .= "\n"; + return $expout; + } +} + +?> \ No newline at end of file diff --git a/question/format/clozejr/lang/en_utf8/clozejr.php b/question/format/clozejr/lang/en_utf8/clozejr.php new file mode 100644 index 00000000000..726ca461ef4 --- /dev/null +++ b/question/format/clozejr/lang/en_utf8/clozejr.php @@ -0,0 +1,6 @@ + $a type into a Cloze question.'; +$string['displayclozequestion'] = 'And here is the new Cloze question (with correct answers inserted):'; +$string['displayquestions'] = 'The following questions from category $a will be used in the new Cloze question:'; +$string['donotclick'] = 'Do not click on the link below, because there is no exported file...
Click on the Continue button. You will find the new Cloze question in your $a category.'; +?> \ No newline at end of file diff --git a/question/format/clozejr/lang/fr_utf8/clozejr.php b/question/format/clozejr/lang/fr_utf8/clozejr.php new file mode 100644 index 00000000000..863e007bd10 --- /dev/null +++ b/question/format/clozejr/lang/fr_utf8/clozejr.php @@ -0,0 +1,10 @@ +$a dans une question de type Cloze.'; +$string['displayclozequestion'] = 'Et voici la nouvelle question Cloze (avec les bonnes réponses insérées entre accolades).'; +$string['displayquestions'] = 'Les questions suivantes de la catégorie $a seront utilisées pour la nouvelle question Cloze :'; +$string['donotclick'] = 'Ne cliquez pas sur le lien ci-dessous, parce qu\'il n\'y a pas de fichier exporté...
Cliquez sur le bouton Continuer. Vous retrouverez la nouvelle question Cloze dans votre catégorie $a .'; + +?>