Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

closejr small hack to "pseudo-export" the questions inside a category…

… into a new, unique cloze/multianswer question - by Joseph Rézeau
  • Loading branch information...
commit 62382645cba96f26c4a933bdf4e6025fd638fa17 1 parent 3a6a84b
authored April 06, 2008
470  question/format/clozejr/format.php
... ...
@@ -0,0 +1,470 @@
  1
+<?php // $Id$
  2
+//
  3
+///////////////////////////////////////////////////////////////
  4
+// imports all the questions from an XML question category file into a unique close/embedded question
  5
+//////////////////////////////////////////////////////////////////////////
  6
+// Based on default.php, included by ../import.php
  7
+
  8
+require_once( "$CFG->libdir/xmlize.php" );
  9
+    global $CFG, $langfile;
  10
+    $langfile = $CFG->dirroot.'/question/format/clozejr/lang/';
  11
+
  12
+class qformat_clozejr extends qformat_default {
  13
+    function provide_import() {
  14
+        return false;
  15
+    }
  16
+    function provide_export() {
  17
+        return true;
  18
+    }
  19
+
  20
+    // IMPORT FUNCTIONS START HERE
  21
+
  22
+    function importprocess($lines) {
  23
+        $questions = $this->readquestions($lines);
  24
+        // Now process and store the unique cloze question
  25
+        $question = $questions[0];
  26
+        $question->category = $this->category->id;
  27
+        $question->stamp = make_unique_id_code();  // Set the unique code (not to be changed)
  28
+        if (!$question->id = insert_record("question", $question)) {
  29
+            error( get_string('cannotinsert','quiz') );
  30
+        }
  31
+        $this->questionids[] = $question->id;
  32
+        // Now to save all the answers and type-specific options
  33
+        global $QTYPES;
  34
+		$question->course = '';
  35
+        $result = $QTYPES[$question->qtype]
  36
+                ->save_question_options($question);
  37
+
  38
+        if (!empty($result->error)) {
  39
+            notify($result->error);
  40
+            return false;
  41
+        }
  42
+
  43
+        if (!empty($result->notice)) {
  44
+            notify($result->notice);
  45
+            return true;
  46
+        }
  47
+        // Give the question a unique version stamp determined by question_hash()
  48
+        set_field('question', 'version', question_hash($question), 'id', $question->id);
  49
+
  50
+        return true;
  51
+    }
  52
+    
  53
+    function import_text( $text ) {
  54
+        $data = $text[0]['#'];
  55
+        $data = html_entity_decode( $data );
  56
+        return trim( $data );
  57
+    }
  58
+
  59
+    function import_answer( $answer ) {
  60
+    // import answer part of question
  61
+
  62
+        $fraction = $answer['@']['fraction'];
  63
+        $text = $this->import_text( $answer['#']['text']);
  64
+        $feedback = $this->import_text( $answer['#']['feedback'][0]['#']['text'] );
  65
+
  66
+        $ans = null;
  67
+        $ans->answer = $text;
  68
+        $ans->fraction = $fraction / 100;
  69
+        $ans->feedback = $feedback;
  70
+  
  71
+        return $ans;
  72
+    }
  73
+    
  74
+    function import_question( $question ) {
  75
+    // run through the answers
  76
+        $answers = $question['#']['answer'];
  77
+		$an = '';  
  78
+        foreach ($answers as $answer) {
  79
+            $ans = $this->import_answer( $answer );
  80
+            $fraction = $ans->fraction * 100;
  81
+            $an.="~%".$fraction."%".$ans->answer."#".$ans->feedback;
  82
+        }
  83
+        return $an;
  84
+    }
  85
+    
  86
+    function import_numerical( $question ) {
  87
+    // import numerical question
  88
+        // get answers array
  89
+        $answers = $question['#']['answer'];
  90
+        $an = '';
  91
+        foreach ($answers as $answer) {
  92
+            $fraction = trim($answer['#']['fraction'][0]['#']);
  93
+            $ans = trim($answer['#'][0]);
  94
+            $tolerance = trim($answer['#']['tolerance'][0]['#']);
  95
+            $feedback = addslashes(trim(( $answer['#']['feedback'][0]['#']['text'][0]['#'] )));
  96
+            $percent = $fraction * 100;
  97
+            $an.="~%$percent%".$ans.":".$tolerance."#".$feedback;
  98
+        }
  99
+        return $an;
  100
+    }
  101
+
  102
+    function reverse_strrchr($haystack, $needle)
  103
+    {
  104
+       $pos = strrpos($haystack, $needle);
  105
+       if($pos === false) {
  106
+           return $haystack;
  107
+       }
  108
+       return substr($haystack, 0, $pos + 0).'<br />';
  109
+    }
  110
+
  111
+    function readquestions($lines) {
  112
+        // this converts xml to big nasty data structure
  113
+        // the 0 means keep white space as it is (important for markdown format)
  114
+        // print_r it if you want to see what it looks like!
  115
+        global $CFG, $langfile;
  116
+        $xml = xmlize( $lines, 0 ); 
  117
+        // set up array to hold all our questions
  118
+        $questions = array();
  119
+        $subquestions = array();
  120
+        $clozequestionname = $xml['QUESTION_CATEGORY']['#']['NAME'][0]['#'];
  121
+        notify(get_string( 'displayclozequestion','clozejr','',$langfile) );
  122
+
  123
+        // iterate through questions
  124
+        $count = 0;
  125
+        $numberthequestions = false;
  126
+        foreach ($xml['QUESTION_CATEGORY']['#']['question'] as $question) {
  127
+            $count++;
  128
+            $questiontype = $question['@']['type'];
  129
+            $an = null;
  130
+            $defaultgrade = $question['#']['DEFAULTGRADE'][0]['#'];
  131
+            switch($questiontype) {
  132
+                CASE 'numerical' :
  133
+                    $an = $this->import_numerical( $question );
  134
+                    break;
  135
+                CASE 'description' :
  136
+                    break;
  137
+                CASE 'multichoice':
  138
+                CASE 'regexp':
  139
+                CASE 'shortanswer':
  140
+                    $an = addslashes( $this->import_question($question) ); 
  141
+                    break;
  142
+                CASE 'matching' :
  143
+                CASE 'cloze' :
  144
+                default:
  145
+                    $an = null;
  146
+            }
  147
+            // stick the result in the $questions array
  148
+			$isnewline = false;
  149
+            if ($an OR ($questiontype == 'description')) {
  150
+				//addslashes added JR 29 MAR 2007
  151
+                $qtext = addslashes($this->import_text( $question['#']['questiontext'][0]['#']['text'] ) );
  152
+                $qtext = ereg_replace("(</?p>|</?br ?/?>|\n)", "", $qtext);
  153
+                // remove all <p></p> tags and potential newline \n from question text ... but keeps existing <hr> tags if any
  154
+				$newline = '';
  155
+				$pos = strrpos($qtext, '<hr');
  156
+				if ($pos) {
  157
+					$qtext = substr($qtext, 0, $pos + 0);
  158
+					$newline = '<br />';
  159
+				}
  160
+
  161
+                // if question text ends with horizontal rule, then replace <hr> with <br />
  162
+
  163
+                $qtext_array = explode('***', $qtext,2);
  164
+                // replace "blank" *** sequences in original question text with question slot
  165
+				
  166
+                $name = $this->import_text( $question['#']['name'][0]['#']['text'] );
  167
+                if ($count === 1 and substr($name, 2, 1) == '-') {
  168
+                // only number questions if the very first one is something like '01-'
  169
+                    $numberthequestions = true;
  170
+                }
  171
+                $n = "00";
  172
+                $nb = substr($name, 0, 2);
  173
+                // get first 2 letters of question name - if 01, 02, ... 99, then change into 01- ... 99-
  174
+                if ($nb + 0 != 0) {
  175
+                        $n = $nb.'- ';
  176
+                    } else {
  177
+						$n = '';
  178
+				}
  179
+                if ($questiontype == 'description') {
  180
+                    $subquestions[] = $n.$qtext.$newline;
  181
+                } else {
  182
+                    $subquestions[] = $n.$qtext_array[0]."{".$defaultgrade.":".strtoupper($questiontype)
  183
+						.":".$an."}".$qtext_array[1].$newline;
  184
+                }
  185
+            }
  186
+        }
  187
+        sort($subquestions);
  188
+        if ($numberthequestions == false) {
  189
+            $count = 0;
  190
+            foreach ($subquestions as $question) {
  191
+                $subquestions[$count] = substr($question,3);
  192
+                $count++;
  193
+            }
  194
+        }
  195
+        $subquestions = implode(" ",$subquestions);
  196
+        $qo = qtype_multianswer_extract_question($subquestions);
  197
+        $qo->name = $clozequestionname;
  198
+        // the cloze question is named after the name of the question_category from which the questions were exported
  199
+        $questions[] = $qo;
  200
+        //now replace all {#} blanks in question text with correct answwer (if more than one, first met is selected)
  201
+        $qtext = $qo->questiontext;
  202
+        $countquestions = 1;
  203
+        foreach ($qo->options->questions as $question) {
  204
+            $count = 0;
  205
+            foreach ($question->answer as $answer) {
  206
+                if ($question->fraction[$count] == 1) {
  207
+                    $qtext = ereg_replace("\{\#".$countquestions."\}", "{".$answer."}", $qtext);
  208
+                    break;
  209
+                }
  210
+                $count++;
  211
+            }
  212
+            $countquestions++;
  213
+        }
  214
+        echo "<p>".stripslashes($qtext)."</p>";
  215
+        // print it here rather than in importprocess()
  216
+        return $questions;
  217
+    }
  218
+
  219
+    // EXPORT FUNCTIONS START HERE
  220
+
  221
+   function exportprocess() {
  222
+    /// Exports a given category.  There's probably little need to change this
  223
+        global $CFG, $langfile;
  224
+
  225
+        // create a directory for the exports (if not already existing)
  226
+        if (! $export_dir = make_upload_directory($this->question_get_export_dir())) {
  227
+              error( get_string('cannotcreatepath','quiz',$export_dir) );
  228
+        }
  229
+        $path = $CFG->dataroot.'/'.$this->question_get_export_dir();
  230
+
  231
+        // get the questions (from database) in this category
  232
+        // only get q's with no parents (no cloze subquestions specifically)
  233
+        $questions = get_questions_category( $this->category, true );
  234
+        notify( get_string('displayquestions', 'clozejr', '', $langfile) );
  235
+
  236
+        if (!count($questions)) {
  237
+            notify( get_string('noquestions','quiz') );
  238
+            return false;
  239
+        }
  240
+        $count = 0;
  241
+
  242
+        // results are first written into string (and then to a file)
  243
+        // so create/initialize the string here
  244
+        $expout = "";
  245
+
  246
+        // iterate through questions
  247
+        foreach($questions as $question) {
  248
+            // do not export hidden questions
  249
+            if (!empty($question->hidden)) {
  250
+                continue;
  251
+            }
  252
+
  253
+            // do not export questions of these types 
  254
+            $questiontype = $question->qtype; 
  255
+			$questiontypename = $this->get_qtype($questiontype);
  256
+            if ($questiontype==RANDOM or $questiontype==MULTIANSWER or $questiontype==MATCH or $questiontype==TRUEFALSE or $questiontype==ESSAY or $questiontype==RANDOMSAMATCH) {
  257
+                notify( get_string( 'clozetypeunsupported', 'clozejr', '', $langfile) );
  258
+                continue;
  259
+            }
  260
+
  261
+        // export the question displaying message
  262
+        $count++;
  263
+        $qtext = stripslashes($question->questiontext);
  264
+        $qtext = ereg_replace("</?p>", "", $qtext);
  265
+        
  266
+        echo "<p><b>$count</b>. ".$qtext."</p>";
  267
+        $expout .= $this->writequestion( $question ) . "\n";
  268
+        }
  269
+
  270
+        // final pre-process on exported data
  271
+        $expout = $this->presave_process( $expout );
  272
+        $result = $this->importprocess ($expout);
  273
+        return true;
  274
+    }
  275
+	
  276
+	    function get_qtype( $type_id ) {
  277
+    // translates question type code number into actual name
  278
+   
  279
+        switch( $type_id ) {
  280
+        case TRUEFALSE:
  281
+            $name = 'truefalse';
  282
+            break;
  283
+        case MULTICHOICE:
  284
+            $name = 'multichoice';
  285
+            break;
  286
+        case SHORTANSWER:
  287
+            $name = 'shortanswer';
  288
+            break;
  289
+        case 'regexp':
  290
+            $name = 'regexp';
  291
+            break;
  292
+        case NUMERICAL:
  293
+            $name = 'numerical';
  294
+            break;
  295
+        case MATCH:
  296
+            $name = 'matching';
  297
+            break;
  298
+        case DESCRIPTION:
  299
+            $name = 'description';
  300
+            break;
  301
+        case ESSAY:
  302
+            $name = 'essay';
  303
+            break;
  304
+        case MULTIANSWER:
  305
+            $name = 'cloze';
  306
+            break;
  307
+        default:
  308
+            $name = 'unknown';
  309
+        }
  310
+        return $name;
  311
+    }
  312
+
  313
+
  314
+    function get_single( $id ) {
  315
+    // translate single value into something sensible
  316
+
  317
+        switch( $id ) {
  318
+        case 0:
  319
+            $name = "false";
  320
+            break;
  321
+        case 1:
  322
+            $name = "true";
  323
+            break;
  324
+        default:
  325
+            $name = "unknown";
  326
+        }
  327
+        return $name;
  328
+    }
  329
+
  330
+    function writetext( $raw, $ilev=0, $short=true) {
  331
+    // generates <text></text> tags, processing raw text therein 
  332
+    // $ilev is the current indent level
  333
+    // $short=true sticks it on one line
  334
+        $indent = str_repeat( "  ",$ilev );
  335
+
  336
+        // encode the text to 'disguise' HTML content 
  337
+        $raw = htmlspecialchars( $raw );
  338
+
  339
+        if ($short) {
  340
+            $xml = "$indent<text>$raw</text>\n";
  341
+        }
  342
+        else {
  343
+            $xml = "$indent<text>\n$raw\n$indent</text>\n";
  344
+        }
  345
+
  346
+        return $xml;
  347
+    }
  348
+
  349
+    function presave_process( $content ) {
  350
+    // override method to allow us to add xml headers and footers
  351
+        global $categoryname;
  352
+        $categoryname = '';
  353
+        $categoryid = optional_param('category',0, PARAM_INT);
  354
+        if (!$categories = get_records_select("question_categories", "id = '{$categoryid}'")) {
  355
+            error("Could not find any question categories!");
  356
+        }
  357
+        $categories = add_indented_names($categories);
  358
+        foreach ($categories as $key => $cat) {
  359
+            $categoryname = $cat->name;
  360
+        }
  361
+        notify ("categoryname = $categoryname");
  362
+        $categoryid = 1;
  363
+        $content = "<?xml version=\"1.0\"?>\n" .
  364
+                       "<QUESTION_CATEGORY>\n" .
  365
+                       "<ID>".$categoryid."</ID>\n" .
  366
+                       "<NAME>".$categoryname."</NAME>\n" .
  367
+                       $content . "\n" .
  368
+                       "</QUESTION_CATEGORY>";
  369
+        return $content;
  370
+    }
  371
+
  372
+    function exportpostprocess() {
  373
+    /// Does any post-processing that may be desired
  374
+        global $langfile, $categoryname;
  375
+        notify( get_string('donotclick', 'clozejr', $categoryname, $langfile) );
  376
+        return true;
  377
+    }
  378
+
  379
+    function writequestion( $question ) {
  380
+    // turns question into string
  381
+    // initial string;
  382
+        $expout = "";
  383
+        $questiontype = $question->qtype;
  384
+        switch( $questiontype ) {
  385
+            case MULTICHOICE:
  386
+                if ($this->get_single($question->options->single) == 'true') {
  387
+                    break;
  388
+                }
  389
+            case SHORTANSWER:
  390
+            case 'regexp':
  391
+            case DESCRIPTION:
  392
+            case NUMERICAL:
  393
+                break;
  394
+            case MATCH:
  395
+            case MULTIANSWER: //cloze/embedded
  396
+            case RANDOMSAMATCH:
  397
+            case TRUEFALSE:
  398
+            default:
  399
+                notify( get_string( 'clozetypeunsupported','clozejr',$questiontype) );
  400
+        }
  401
+
  402
+        if ($questiontype == 'unsupported') {
  403
+            return null;
  404
+        }
  405
+        // add comment
  406
+        $expout .= "\n\n<!-- question: $question->id  -->\n";
  407
+        $defaultgrade = $question->defaultgrade;
  408
+        // add opening tag
  409
+        // generates specific header for Cloze type question
  410
+
  411
+        $nametext = $this->writetext( $question->name );
  412
+        $qtformat = "";
  413
+        $questiontext = $this->writetext( $question->questiontext );
  414
+        $expout .= "  <question type=\"$questiontype\">\n";   
  415
+        $expout .= "    <name>$nametext</name>\n";
  416
+        $expout .= "    <DEFAULTGRADE>".$defaultgrade."</DEFAULTGRADE>\n";
  417
+        $expout .= "    <questiontext>\n".$questiontext;
  418
+        $expout .= "    </questiontext>\n";   
  419
+        // output depends on question type
  420
+        switch($question->qtype) {
  421
+        case MULTICHOICE:
  422
+        case SHORTANSWER:
  423
+        case 'regexp':
  424
+            foreach($question->options->answers as $answer) {
  425
+                $percent = $answer->fraction * 100;
  426
+                $expout .= "      <answer fraction=\"$percent\">\n";
  427
+                $expout .= $this->writetext( $answer->answer,4,false );
  428
+                $expout .= "      <feedback>\n";
  429
+                $expout .= $this->writetext( $answer->feedback,4,false );
  430
+                $expout .= "      </feedback>\n";
  431
+                $expout .= "    </answer>\n";
  432
+                }
  433
+            break;
  434
+        case NUMERICAL:
  435
+            foreach ($question->options->answers as $answer) {
  436
+                $tolerance = $answer->tolerance;
  437
+                $expout .= "<answer>\n";
  438
+                $expout .= "    {$answer->answer}\n";
  439
+                $expout .= "    <tolerance>$tolerance</tolerance>\n";
  440
+                $expout .= "    <feedback>".$this->writetext( $answer->feedback )."</feedback>\n";
  441
+                $expout .= "    <fraction>{$answer->fraction}</fraction>\n";
  442
+                $expout .= "</answer>\n";
  443
+            }
  444
+
  445
+            $units = $question->options->units;
  446
+            if (count($units)) {
  447
+                $expout .= "<units>\n";
  448
+                foreach ($units as $unit) {
  449
+                    $expout .= "  <unit>\n";
  450
+                    $expout .= "    <multiplier>{$unit->multiplier}</multiplier>\n";
  451
+                    $expout .= "    <unit_name>{$unit->unit}</unit_name>\n";
  452
+                    $expout .= "  </unit>\n";
  453
+                }
  454
+                $expout .= "</units>\n";
  455
+            }
  456
+            break;
  457
+        case DESCRIPTION:
  458
+            // nothing more to do for this type
  459
+            break;
  460
+        default:
  461
+            $expout .= "<!-- Question type is unknown or not supported (Type=$question->qtype) -->\n";
  462
+        }
  463
+
  464
+        // close the question tag
  465
+        $expout .= "</question>\n";
  466
+        return $expout;
  467
+    }
  468
+}
  469
+
  470
+?>
6  question/format/clozejr/lang/en_utf8/clozejr.php
... ...
@@ -0,0 +1,6 @@
  1
+<?php
  2
+$string['clozetypeunsupported'] = 'Cannot insert a  question of <em> $a </em> type into a Cloze question.';
  3
+$string['displayclozequestion'] = 'And here is the new Cloze question (with correct answers inserted):';
  4
+$string['displayquestions'] = 'The following questions from category <em> $a </em> will be used in the new Cloze question:';
  5
+$string['donotclick'] = 'Do <strong>not</strong> click on the link below, because there is no exported file...<br />Click on the <strong>Continue</strong> button. You will find the new Cloze question in your <em> $a </em> category.';
  6
+?>
10  question/format/clozejr/lang/fr_utf8/clozejr.php
... ...
@@ -0,0 +1,10 @@
  1
+<?PHP // $Id$ 
  2
+      // cloze_jr.php - created with Moodle 1.6.1+ (2006050511)
  3
+
  4
+
  5
+$string['clozetypeunsupported'] = 'Impossible d\'insérer une question de type <em>$a</em> dans une question de type Cloze.';
  6
+$string['displayclozequestion'] = 'Et voici la nouvelle question Cloze (avec les bonnes réponses insérées entre accolades).';
  7
+$string['displayquestions'] = 'Les questions suivantes de la catégorie <em>$a</em> seront utilisées pour la nouvelle question Cloze&nbsp:';
  8
+$string['donotclick'] = 'Ne cliquez <strong>pas</strong> sur le lien ci-dessous, parce qu\'il n\'y a pas de fichier exporté...<br />Cliquez sur le bouton  <strong>Continuer</strong>. Vous retrouverez la nouvelle question Cloze dans votre catégorie <em> $a </em>.';
  9
+
  10
+?>

0 notes on commit 6238264

Please sign in to comment.
Something went wrong with that request. Please try again.