MDL-20636 It is now possible to save a truefalse question you have created.
authorTim Hunt <T.J.Hunt@open.ac.uk>
Fri, 24 Dec 2010 13:22:26 +0000 (13:22 +0000)
committerTim Hunt <T.J.Hunt@open.ac.uk>
Thu, 13 Jan 2011 18:35:41 +0000 (18:35 +0000)
This includes all the necessary DB upgrades.

Also all unit tests in question/type/simpletest and question/type/truefalse now pass.

37 files changed:
lang/en/question.php
lib/moodlelib.php
lib/questionlib.php
local/qedatabase/db/install.php [new file with mode: 0755]
local/qedatabase/readme.txt [new file with mode: 0755]
local/qedatabase/version.php [new file with mode: 0755]
question/behaviour/adaptive/lang/en/qbehaviour_adaptive.php [moved from question/behaviour/adaptive/lang/en_utf8/qbehaviour_adaptive.php with 100% similarity]
question/behaviour/adaptivenopenalty/lang/en/qbehaviour_adaptivenopenalty.php [moved from question/behaviour/adaptivenopenalty/lang/en_utf8/qbehaviour_adaptivenopenalty.php with 100% similarity]
question/behaviour/behaviourbase.php
question/behaviour/deferredcbm/lang/en/qbehaviour_deferredcbm.php [moved from question/behaviour/deferredcbm/lang/en_utf8/qbehaviour_deferredcbm.php with 68% similarity]
question/behaviour/deferredfeedback/lang/en/qbehaviour_deferredfeedback.php [moved from question/behaviour/deferredfeedback/lang/en_utf8/qbehaviour_deferredfeedback.php with 100% similarity]
question/behaviour/immediatecbm/lang/en/qbehaviour_immediatecbm.php [moved from question/behaviour/immediatecbm/lang/en_utf8/qbehaviour_immediatecbm.php with 100% similarity]
question/behaviour/immediatefeedback/lang/en/qbehaviour_immediatefeedback.php [moved from question/behaviour/immediatefeedback/lang/en_utf8/qbehaviour_immediatefeedback.php with 100% similarity]
question/behaviour/informationitem/lang/en/qbehaviour_informationitem.php [moved from question/behaviour/informationitem/lang/en_utf8/qbehaviour_informationitem.php with 100% similarity]
question/behaviour/interactive/lang/en/qbehaviour_interactive.php [moved from question/behaviour/interactive/lang/en_utf8/qbehaviour_interactive.php with 100% similarity]
question/behaviour/interactivecountback/lang/en/qbehaviour_interactivecountback.php [moved from question/behaviour/interactivecountback/lang/en_utf8/qbehaviour_interactivecountback.php with 100% similarity]
question/behaviour/manualgraded/lang/en/qbehaviour_manualgraded.php [moved from question/behaviour/manualgraded/lang/en_utf8/qbehaviour_manualgraded.php with 100% similarity]
question/behaviour/missing/lang/en/qbehaviour_missing.php [moved from question/behaviour/missing/lang/en_utf8/qbehaviour_missing.php with 100% similarity]
question/behaviour/opaque/lang/en/qbehaviour_opaque.php [moved from question/behaviour/opaque/lang/en_utf8/qbehaviour_opaque.php with 100% similarity]
question/behaviour/rendererbase.php
question/engine/lib.php
question/todo/diffstat.txt
question/type/edit_question_form.php
question/type/missingtype/lang/en/qtype_missingtype.php
question/type/old_questiontype.php [deleted file]
question/type/question.html [deleted file]
question/type/questionbase.php
question/type/rendererbase.php
question/type/simpletest/testquestionbase.php [new file with mode: 0644]
question/type/truefalse/display.html [deleted file]
question/type/truefalse/edit_truefalse_form.php
question/type/truefalse/lang/en/qtype_truefalse.php
question/type/truefalse/old_questiontype.php [deleted file]
question/type/truefalse/questiontype.php
question/type/truefalse/renderer.php
question/type/truefalse/simpletest/testquestion.php [new file with mode: 0644]
question/type/truefalse/simpletest/testquestiontype.php [new file with mode: 0644]

index e262ae3..a7338d4 100644 (file)
@@ -232,3 +232,108 @@ $string['upgradeproblemunknowncategory'] = 'Problem detected when upgrading ques
 $string['wrongprefix'] = 'Wrongly formatted nameprefix {$a}';
 $string['youmustselectaqtype'] = 'You must select a question type.';
 $string['yourfileshoulddownload'] = 'Your export file should start to download shortly. If not, please <a href="{$a}">click here</a>.';
+
+$string['action'] = 'Action';
+$string['addanotherhint'] = 'Add another hint';
+$string['answer'] = 'Answer';
+$string['answersaved'] = 'Answer saved';
+$string['attemptfinished'] = 'Attempt finished';
+$string['attemptfinishedsubmitting'] = 'Attempt finished submitting: ';
+$string['behaviourbeingused'] = 'behaviour being used: $a';
+$string['cannotloadquestion'] = 'Could not load question';
+$string['cannotpreview'] = 'You can\'t preview these questions!';
+$string['category'] = 'Category';
+$string['changeoptions'] = 'Change options';
+$string['check'] = 'Check';
+$string['clearwrongparts'] = 'Clear incorrect responses';
+$string['clicktoflag'] = 'Click to flag this question';
+$string['clicktounflag'] = 'Click to un-flag this question';
+$string['closepreview'] = 'Close preview';
+$string['combinedfeedback'] = 'Combined feedback';
+$string['commented'] = 'Commented: {$a}';
+$string['comment'] = 'Comment';
+$string['commentormark'] = 'Make comment or override mark';
+$string['comments'] = 'Comments';
+$string['commentx'] = 'Comment: $a';
+$string['complete'] = 'Complete';
+$string['contexterror'] = 'You shouldn\'t have got here if you\'re not moving a category to another context.';
+$string['correct'] = 'Correct';
+$string['correctfeedback'] = 'For any correct response';
+$string['decimalplacesingrades'] = 'Decimal places in grades';
+$string['defaultmark'] = 'Default mark';
+$string['feedback'] = 'Feedback';
+$string['fillincorrect'] = 'Fill in correct responses';
+$string['flagged'] = 'Flagged';
+$string['flagthisquestion'] = 'Flag this question';
+$string['generalfeedback'] = 'General feedback';
+$string['generalfeedback_help'] = 'General feedback is shown to the student after they have attempted the question. Unlike feedback, which depends on the question type and what response the student gave, the same general feedback text is shown to all students.
+
+You can use the general feedback to give students some background to what knowledge the question was testing, or give them a link to more information they can use if they did not understand the questions.';
+$string['hidden'] = 'Hidden';
+$string['hintn'] = 'Hint {no}';
+$string['hinttext'] = 'Hint text';
+$string['howquestionsbehave'] = 'How questions behave';
+$string['importfromcoursefiles'] = '... or choose a course file to import.';
+$string['importfromupload'] = 'Select a file to upload ...';
+$string['incorrect'] = 'Incorrect';
+$string['incorrectfeedback'] = 'For any incorrect response';
+$string['information'] = 'Information';
+$string['invalidanswer'] = 'Incomplete answer';
+$string['makecopy'] = 'Make copy';
+$string['manualgradeoutofrange'] = 'This grade is outside the valid range.';
+$string['manuallygraded'] = 'Manually graded {$a->mark} with comment: {$a->comment}';
+$string['mark'] = 'Mark';
+$string['markedoutof'] = 'Marked out of';
+$string['markedoutofmax'] = 'Marked out of $a';
+$string['markoutofmax'] = 'Mark $a->mark out of $a->max';
+$string['marks'] = 'Marks';
+$string['noresponse'] = '[No response]';
+$string['notanswered'] = 'Not answered';
+$string['notflagged'] = 'Not flagged';
+$string['notgraded'] = 'Not graded';
+$string['notshown'] = 'Not shown';
+$string['notyetanswered'] = 'Not yet answered';
+$string['notyourpreview'] = 'This preview does not belong to you';
+$string['options'] = 'Options';
+$string['partiallycorrect'] = 'Partially correct';
+$string['partiallycorrectfeedback'] = 'For any partially correct response';
+$string['penaltyforeachincorrecttry'] = 'Penalty for each incorrect try';
+$string['penaltyforeachincorrecttry_help'] = 'When you run your questions using the \'Interactive with multiple tries\' or \'Adaptive mode\' behaviour, so that the the student will have several tries to get the question right, then this option controls how much they are penalised for each incorrect try.
+
+The penalty is a proportion of the total question grade, so if the question is worth three marks, and the penalty is 0.3333333, then the student will score 3 if they get the question right first time, 2 if they get it right second try, and 1 of they get it right on the third try.';
+$string['previewquestion'] = 'Preview question: $a';
+$string['questionbehaviouradminsetting'] = 'Question behaviour settings';
+$string['questionbehavioursdisabled'] = 'Question behaviours to disable';
+$string['questionbehavioursdisabledexplained'] = 'Enter a comma separated list of behaviours you do not want to appear in dropdown menu';
+$string['questionbehavioursorder'] = 'Question behaviours order';
+$string['questionbehavioursorderexplained'] = 'Enter a comma separated list of behaviours in the order you want them to appear in dropdown menu';
+$string['questionidmismatch'] = 'Question ids mismatch';
+$string['questionname'] = 'Question name';
+$string['questionx'] = 'Question $a';
+$string['questiontext'] = 'Question text';
+$string['requiresgrading'] = 'Requires grading';
+$string['responsehistory'] = 'Response history';
+$string['restart'] = 'Start again';
+$string['restartwiththeseoptions'] = 'Start again with these options';
+$string['rightanswer'] = 'Right answer';
+$string['saved'] = 'Saved: {$a}';
+$string['saveflags'] = 'Save the state of the flags';
+$string['settingsformultipletries'] = 'Settings for multiple tries';
+$string['showmarkandmax'] = 'Show mark and max';
+$string['showmaxmarkonly'] = 'Show max mark only';
+$string['shown'] = 'Shown';
+$string['shownumpartscorrect'] = 'Show the number of correct responses';
+$string['specificfeedback'] = 'Specific feedback';
+$string['started'] = 'Started';
+$string['state'] = 'State';
+$string['step'] = 'Step';
+$string['submissionoutofsequence'] = 'Access out of sequence. Please do not click the back button when working on quiz questions.';
+$string['submissionoutofsequencefriendlymessage'] = "You have entered data outside the normal sequence. This can occur if you use your browser's Back or Forward buttons; please don't use these during the test. It can also happen if you click on something while a page is loading. Click <strong>Continue</strong> to resume.";
+$string['submit'] = 'Submit';
+$string['submitandfinish'] = 'Submit and finish';
+$string['submitted'] = 'Submit: {$a}';
+$string['unknownquestion'] = 'Unknown question: $a.';
+$string['unknownquestioncatregory'] = 'Unknown question category: $a.';
+$string['whethercorrect'] = 'Whether correct';
+$string['xoutofmax'] = '$a->mark out of $a->max';
+$string['yougotnright'] = 'You have correctly selected {$a->num}.';
index 30eb421..37d9d2c 100644 (file)
@@ -7229,8 +7229,9 @@ function get_plugin_types($fullpaths=true) {
                       'webservice'    => 'webservice',
                       'repository'    => 'repository',
                       'portfolio'     => 'portfolio',
-                      'qtype'         => 'question/type',
+                      'qbehaviour'    => 'question/behaviour',
                       'qformat'       => 'question/format',
+                      'qtype'         => 'question/type',
                       'plagiarism'    => 'plagiarism',
                       'theme'         => 'theme'); // this is a bit hacky, themes may be in dataroot too
 
index ddeb74f..3793083 100644 (file)
@@ -1717,7 +1717,7 @@ class question_edit_contexts {
  * @param array $options
  * @return string
  */
-function quiz_rewrite_question_urls($text, $file, $contextid, $component, $filearea, array $ids, $itemid, array $options=null) {
+function question_rewrite_question_urls($text, $file, $contextid, $component, $filearea, array $ids, $itemid, array $options=null) {
     global $CFG;
 
     $options = (array)$options;
diff --git a/local/qedatabase/db/install.php b/local/qedatabase/db/install.php
new file mode 100755 (executable)
index 0000000..a4d4730
--- /dev/null
@@ -0,0 +1,732 @@
+<?php
+
+function xmldb_local_qedatabase_install() {
+    global $DB;
+    $dbman = $DB->get_manager();
+
+    // Bit of a hack to prevent errors like "Cannot downgrade local_qedatabase from ... to ...".
+    $oldversion = 2008000000;
+    $DB->set_field('config_plugins', 'value', $oldversion,
+            array('plugin' => 'local_qedatabase', 'name' => 'version'));
+
+    // Add new preferredbehaviour column to the quiz table.
+    if ($oldversion < 2008000100) {
+        $table = new xmldb_table('quiz');
+        $field = new xmldb_field('preferredbehaviour');
+        $field->set_attributes(XMLDB_TYPE_CHAR, '32', null, null, null, null, 'timeclose');
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000100, 'local', 'qedatabase');
+    }
+
+    // Populate preferredbehaviour column based on old optionflags column.
+    if ($oldversion < 2008000101) {
+        $DB->set_field_select('quiz', 'preferredbehaviour', 'deferredfeedback',
+                'optionflags = 0');
+        $DB->set_field_select('quiz', 'preferredbehaviour', 'adaptive',
+                'optionflags <> 0 AND penaltyscheme <> 0');
+        $DB->set_field_select('quiz', 'preferredbehaviour', 'adaptivenopenalty',
+                'optionflags <> 0 AND penaltyscheme = 0');
+
+        set_config('quiz_preferredbehaviour', 'deferredfeedback');
+        set_config('quiz_fix_preferredbehaviour', 0);
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000101, 'local', 'qedatabase');
+    }
+
+    // Add a not-NULL constraint to the preferredmodel field now that it is populated.
+    if ($oldversion < 2008000102) {
+        $table = new xmldb_table('quiz');
+        $field = new xmldb_field('preferredbehaviour');
+        $field->set_attributes(XMLDB_TYPE_CHAR, '32', null, XMLDB_NOTNULL, null, null, 'timeclose');
+
+        $dbman->change_field_notnull($table, $field);
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000102, 'local', 'qedatabase');
+    }
+
+    // Drop the old optionflags field.
+    if ($oldversion < 2008000103) {
+        $table = new xmldb_table('quiz');
+        $field = new xmldb_field('optionflags');
+        $dbman->drop_field($table, $field);
+
+        unset_config('quiz_optionflags');
+        unset_config('quiz_fix_optionflags');
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000103, 'local', 'qedatabase');
+    }
+
+    // Drop the old penaltyscheme field.
+    if ($oldversion < 2008000104) {
+        $table = new xmldb_table('quiz');
+        $field = new xmldb_field('penaltyscheme');
+        $dbman->drop_field($table, $field);
+
+        unset_config('quiz_penaltyscheme');
+        unset_config('quiz_fix_penaltyscheme');
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000104, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000110) {
+
+        // Changing nullability of field sumgrades on table quiz_attempts to null
+        $table = new xmldb_table('quiz_attempts');
+        $field = new xmldb_field('sumgrades');
+        $field->set_attributes(XMLDB_TYPE_NUMBER, '10, 5', null, null, null, null, 'attempt');
+
+        // Launch change of nullability for field sumgrades
+        $dbman->change_field_notnull($table, $field);
+
+        // Launch change of default for field sumgrades
+        $dbman->change_field_default($table, $field);
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000110, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000111) {
+
+        // Changing the default of field penalty on table question to 0.3333333
+        $table = new xmldb_table('question');
+        $field = new xmldb_field('penalty');
+        $field->set_attributes(XMLDB_TYPE_FLOAT, null, null, XMLDB_NOTNULL, null, '0.3333333', 'defaultgrade');
+
+        // Launch change of default for field penalty
+        $dbman->change_field_default($table, $field);
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000111, 'local', 'qedatabase');
+    }
+
+// Update the quiz from the old single review column to seven new columns.
+
+    if ($oldversion < 2008000200) {
+
+        // Define field reviewattempt to be added to quiz
+        $table = new xmldb_table('quiz');
+        $field = new xmldb_field('reviewattempt');
+        $field->set_attributes(XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', 'review');
+
+        // Launch add field reviewattempt
+        $dbman->add_field($table, $field);
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000200, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000201) {
+
+        // Define field reviewattempt to be added to quiz
+        $table = new xmldb_table('quiz');
+        $field = new xmldb_field('reviewcorrectness');
+        $field->set_attributes(XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', 'reviewattempt');
+
+        // Launch add field reviewattempt
+        $dbman->add_field($table, $field);
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000201, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000202) {
+
+        // Define field reviewattempt to be added to quiz
+        $table = new xmldb_table('quiz');
+        $field = new xmldb_field('reviewmarks');
+        $field->set_attributes(XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', 'reviewcorrectness');
+
+        // Launch add field reviewattempt
+        $dbman->add_field($table, $field);
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000202, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000203) {
+
+        // Define field reviewattempt to be added to quiz
+        $table = new xmldb_table('quiz');
+        $field = new xmldb_field('reviewspecificfeedback');
+        $field->set_attributes(XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', 'reviewmarks');
+
+        // Launch add field reviewattempt
+        $dbman->add_field($table, $field);
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000203, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000204) {
+
+        // Define field reviewattempt to be added to quiz
+        $table = new xmldb_table('quiz');
+        $field = new xmldb_field('reviewgeneralfeedback');
+        $field->set_attributes(XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', 'reviewspecificfeedback');
+
+        // Launch add field reviewattempt
+        $dbman->add_field($table, $field);
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000204, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000205) {
+
+        // Define field reviewattempt to be added to quiz
+        $table = new xmldb_table('quiz');
+        $field = new xmldb_field('reviewrightanswer');
+        $field->set_attributes(XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', 'reviewgeneralfeedback');
+
+        // Launch add field reviewattempt
+        $dbman->add_field($table, $field);
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000205, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000206) {
+
+        // Define field reviewattempt to be added to quiz
+        $table = new xmldb_table('quiz');
+        $field = new xmldb_field('reviewoverallfeedback');
+        $field->set_attributes(XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', 'reviewrightanswer');
+
+        // Launch add field reviewattempt
+        $dbman->add_field($table, $field);
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000206, 'local', 'qedatabase');
+    }
+
+    define('QUIZ_NEW_DURING',            0x10000);
+    define('QUIZ_NEW_IMMEDIATELY_AFTER', 0x01000);
+    define('QUIZ_NEW_LATER_WHILE_OPEN',  0x00100);
+    define('QUIZ_NEW_AFTER_CLOSE',       0x00010);
+
+    define('QUIZ_OLD_IMMEDIATELY', 0x3c003f);
+    define('QUIZ_OLD_OPEN',        0x3c00fc0);
+    define('QUIZ_OLD_CLOSED',      0x3c03f000);
+
+    define('QUIZ_OLD_RESPONSES',       1*0x1041); // Show responses
+    define('QUIZ_OLD_SCORES',          2*0x1041); // Show scores
+    define('QUIZ_OLD_FEEDBACK',        4*0x1041); // Show question feedback
+    define('QUIZ_OLD_ANSWERS',         8*0x1041); // Show correct answers
+    define('QUIZ_OLD_SOLUTIONS',      16*0x1041); // Show solutions
+    define('QUIZ_OLD_GENERALFEEDBACK',32*0x1041); // Show question general feedback
+    define('QUIZ_OLD_OVERALLFEEDBACK', 1*0x4440000); // Show quiz overall feedback
+
+    // Copy the old review settings
+    if ($oldversion < 2008000210) {
+        $DB->execute("
+            UPDATE {quiz}
+            SET reviewattempt = " . $DB->sql_bitor($DB->sql_bitor(
+                    QUIZ_NEW_DURING,
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_RESPONSES) .
+                        ' <> 0 THEN ' . QUIZ_NEW_IMMEDIATELY_AFTER . ' ELSE 0 END'), $DB->sql_bitor(
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_OPEN & QUIZ_OLD_RESPONSES) .
+                        ' <> 0 THEN ' . QUIZ_NEW_LATER_WHILE_OPEN . ' ELSE 0 END',
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_CLOSED & QUIZ_OLD_RESPONSES) .
+                        ' <> 0 THEN ' . QUIZ_NEW_AFTER_CLOSE . ' ELSE 0 END')) . "
+        ");
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000210, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000211) {
+        $DB->execute("
+            UPDATE {quiz}
+            SET reviewcorrectness = " . $DB->sql_bitor($DB->sql_bitor(
+                    QUIZ_NEW_DURING,
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_SCORES) .
+                        ' <> 0 THEN ' . QUIZ_NEW_IMMEDIATELY_AFTER . ' ELSE 0 END'), $DB->sql_bitor(
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_OPEN & QUIZ_OLD_SCORES) .
+                        ' <> 0 THEN ' . QUIZ_NEW_LATER_WHILE_OPEN . ' ELSE 0 END',
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_CLOSED & QUIZ_OLD_SCORES) .
+                        ' <> 0 THEN ' . QUIZ_NEW_AFTER_CLOSE . ' ELSE 0 END')) . "
+        ");
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000211, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000212) {
+        $DB->execute("
+            UPDATE {quiz}
+            SET reviewmarks = " . $DB->sql_bitor($DB->sql_bitor(
+                    QUIZ_NEW_DURING,
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_SCORES) .
+                        ' <> 0 THEN ' . QUIZ_NEW_IMMEDIATELY_AFTER . ' ELSE 0 END'), $DB->sql_bitor(
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_OPEN & QUIZ_OLD_SCORES) .
+                        ' <> 0 THEN ' . QUIZ_NEW_LATER_WHILE_OPEN . ' ELSE 0 END',
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_CLOSED & QUIZ_OLD_SCORES) .
+                        ' <> 0 THEN ' . QUIZ_NEW_AFTER_CLOSE . ' ELSE 0 END')) . "
+        ");
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000212, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000213) {
+        $DB->execute("
+            UPDATE {quiz}
+            SET reviewspecificfeedback = " . $DB->sql_bitor($DB->sql_bitor(
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_FEEDBACK) .
+                        ' <> 0 THEN ' . QUIZ_NEW_DURING . ' ELSE 0 END',
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_FEEDBACK) .
+                        ' <> 0 THEN ' . QUIZ_NEW_IMMEDIATELY_AFTER . ' ELSE 0 END'), $DB->sql_bitor(
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_OPEN & QUIZ_OLD_FEEDBACK) .
+                        ' <> 0 THEN ' . QUIZ_NEW_LATER_WHILE_OPEN . ' ELSE 0 END',
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_CLOSED & QUIZ_OLD_FEEDBACK) .
+                        ' <> 0 THEN ' . QUIZ_NEW_AFTER_CLOSE . ' ELSE 0 END')) . "
+        ");
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000213, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000214) {
+        $DB->execute("
+            UPDATE {quiz}
+            SET reviewgeneralfeedback = " . $DB->sql_bitor($DB->sql_bitor(
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_GENERALFEEDBACK) .
+                        ' <> 0 THEN ' . QUIZ_NEW_DURING . ' ELSE 0 END',
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_GENERALFEEDBACK) .
+                        ' <> 0 THEN ' . QUIZ_NEW_IMMEDIATELY_AFTER . ' ELSE 0 END'), $DB->sql_bitor(
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_OPEN & QUIZ_OLD_GENERALFEEDBACK) .
+                        ' <> 0 THEN ' . QUIZ_NEW_LATER_WHILE_OPEN . ' ELSE 0 END',
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_CLOSED & QUIZ_OLD_GENERALFEEDBACK) .
+                        ' <> 0 THEN ' . QUIZ_NEW_AFTER_CLOSE . ' ELSE 0 END')) . "
+        ");
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000214, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000215) {
+        $DB->execute("
+            UPDATE {quiz}
+            SET reviewrightanswer = " . $DB->sql_bitor($DB->sql_bitor(
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_ANSWERS) .
+                        ' <> 0 THEN ' . QUIZ_NEW_DURING . ' ELSE 0 END',
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_ANSWERS) .
+                        ' <> 0 THEN ' . QUIZ_NEW_IMMEDIATELY_AFTER . ' ELSE 0 END'), $DB->sql_bitor(
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_OPEN & QUIZ_OLD_ANSWERS) .
+                        ' <> 0 THEN ' . QUIZ_NEW_LATER_WHILE_OPEN . ' ELSE 0 END',
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_CLOSED & QUIZ_OLD_ANSWERS) .
+                        ' <> 0 THEN ' . QUIZ_NEW_AFTER_CLOSE . ' ELSE 0 END')) . "
+        ");
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000215, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000216) {
+        $DB->execute("
+            UPDATE {quiz}
+            SET reviewoverallfeedback = " . $DB->sql_bitor($DB->sql_bitor(
+                    0,
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_OVERALLFEEDBACK) .
+                        ' <> 0 THEN ' . QUIZ_NEW_IMMEDIATELY_AFTER . ' ELSE 0 END'), $DB->sql_bitor(
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_OPEN & QUIZ_OLD_OVERALLFEEDBACK) .
+                        ' <> 0 THEN ' . QUIZ_NEW_LATER_WHILE_OPEN . ' ELSE 0 END',
+                    'CASE WHEN ' . $DB->sql_bitand('review', QUIZ_OLD_CLOSED & QUIZ_OLD_OVERALLFEEDBACK) .
+                        ' <> 0 THEN ' . QUIZ_NEW_AFTER_CLOSE . ' ELSE 0 END')) . "
+        ");
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000216, 'local', 'qedatabase');
+    }
+
+    // And, do the same for the defaults
+    if ($oldversion < 2008000217) {
+        if (empty($CFG->quiz_review)) {
+            $CFG->quiz_review = 0;
+        }
+
+        set_config('quiz_reviewattempt',
+                QUIZ_NEW_DURING |
+                ($CFG->quiz_review & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_RESPONSES ? QUIZ_NEW_IMMEDIATELY_AFTER : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_OPEN & QUIZ_OLD_RESPONSES ? QUIZ_NEW_LATER_WHILE_OPEN : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_CLOSED & QUIZ_OLD_RESPONSES ? QUIZ_NEW_AFTER_CLOSE : 0));
+
+        set_config('quiz_reviewcorrectness',
+                QUIZ_NEW_DURING |
+                ($CFG->quiz_review & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_SCORES ? QUIZ_NEW_IMMEDIATELY_AFTER : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_OPEN & QUIZ_OLD_SCORES ? QUIZ_NEW_LATER_WHILE_OPEN : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_CLOSED & QUIZ_OLD_SCORES ? QUIZ_NEW_AFTER_CLOSE : 0));
+
+        set_config('quiz_reviewmarks',
+                QUIZ_NEW_DURING |
+                ($CFG->quiz_review & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_SCORES ? QUIZ_NEW_IMMEDIATELY_AFTER : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_OPEN & QUIZ_OLD_SCORES ? QUIZ_NEW_LATER_WHILE_OPEN : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_CLOSED & QUIZ_OLD_SCORES ? QUIZ_NEW_AFTER_CLOSE : 0));
+
+        set_config('quiz_reviewspecificfeedback',
+                ($CFG->quiz_review & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_FEEDBACK ? QUIZ_NEW_DURING : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_FEEDBACK ? QUIZ_NEW_IMMEDIATELY_AFTER : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_OPEN & QUIZ_OLD_FEEDBACK ? QUIZ_NEW_LATER_WHILE_OPEN : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_CLOSED & QUIZ_OLD_FEEDBACK ? QUIZ_NEW_AFTER_CLOSE : 0));
+
+        set_config('quiz_reviewgeneralfeedback',
+                ($CFG->quiz_review & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_GENERALFEEDBACK ? QUIZ_NEW_DURING : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_GENERALFEEDBACK ? QUIZ_NEW_IMMEDIATELY_AFTER : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_OPEN & QUIZ_OLD_GENERALFEEDBACK ? QUIZ_NEW_LATER_WHILE_OPEN : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_CLOSED & QUIZ_OLD_GENERALFEEDBACK ? QUIZ_NEW_AFTER_CLOSE : 0));
+
+        set_config('quiz_reviewrightanswer',
+                ($CFG->quiz_review & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_ANSWERS ? QUIZ_NEW_DURING : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_ANSWERS ? QUIZ_NEW_IMMEDIATELY_AFTER : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_OPEN & QUIZ_OLD_ANSWERS ? QUIZ_NEW_LATER_WHILE_OPEN : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_CLOSED & QUIZ_OLD_ANSWERS ? QUIZ_NEW_AFTER_CLOSE : 0));
+
+        set_config('quiz_reviewoverallfeedback',
+                0 |
+                ($CFG->quiz_review & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_OVERALLFEEDBACK ? QUIZ_NEW_IMMEDIATELY_AFTER : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_OPEN & QUIZ_OLD_OVERALLFEEDBACK ? QUIZ_NEW_LATER_WHILE_OPEN : 0) |
+                ($CFG->quiz_review & QUIZ_OLD_CLOSED & QUIZ_OLD_OVERALLFEEDBACK ? QUIZ_NEW_AFTER_CLOSE : 0));
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000217, 'local', 'qedatabase');
+    }
+
+    // Finally drop the old column
+    if ($oldversion < 2008000220) {
+        // Define field review to be dropped from quiz
+        $table = new xmldb_table('quiz');
+        $field = new xmldb_field('review');
+
+        // Launch drop field review
+        $dbman->drop_field($table, $field);
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000220, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000221) {
+        unset_config('quiz_review');
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000221, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000501) {
+
+        // Rename field defaultgrade on table question to defaultmark
+        $table = new xmldb_table('question');
+        $field = new xmldb_field('defaultgrade');
+        $field->set_attributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '1', 'generalfeedback');
+
+        // Launch rename field defaultmark
+        $dbman->rename_field($table, $field, 'defaultmark');
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000501, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000505) {
+
+        // Rename the question_attempts table to question_usages.
+        $table = new xmldb_table('question_attempts');
+        if (table_exists($table)) {
+            $dbman->rename_table($table, 'question_usages');
+        }
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000505, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000507) {
+
+        // Rename the modulename field to component ...
+        $table = new xmldb_table('question_usages');
+        $field = new xmldb_field('modulename');
+        $field->set_attributes(XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, 'contextid');
+        $dbman->rename_field($table, $field, 'component');
+
+        // ... and update its contents.
+        $DB->set_field('question_usages', 'component', 'mod_quiz', array('component' => 'quiz'));
+
+        // Add the contextid field.
+        $field = new xmldb_field('contextid');
+        $field->set_attributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, 'id');
+        $dbman->add_field($table, $field);
+
+        // And populate it.
+        $quizmoduleid = $DB->get_field('modules', 'id', array('name' => 'quiz'));
+        $DB->execute("
+            UPDATE {question_usages} SET contextid = (
+                SELECT ctx.id
+                FROM {context} ctx
+                JOIN {course_modules} cm ON cm.id = ctx.instanceid AND cm.module = $quizmoduleid
+                JOIN {quiz_attempts} quiza ON quiza.quiz = cm.instance
+                WHERE ctx.contextlevel = " . CONTEXT_MODULE . "
+                AND quiza.uniqueid = {question_usages}.id
+            )
+        ");
+
+        // Then make it NOT NULL.
+        $field = new xmldb_field('contextid');
+        $field->set_attributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, 'id');
+        $dbman->change_field_notnull($table, $field);
+
+        // Add the preferredbehaviour column. Populate it with a dummy value
+        // for now. We will fill in the appropriate behaviour name when
+        // updating all the rest of the attempt data.
+        $field = new xmldb_field('preferredbehaviour');
+        $field->set_attributes(XMLDB_TYPE_CHAR, '32', null, null, null, null, null, 'to_be_set_later', 'component');
+        $dbman->add_field($table, $field);
+
+        // Then remove the default value, now the column is populated.
+        $field = new xmldb_field('preferredbehaviour');
+        $field->set_attributes(XMLDB_TYPE_CHAR, '32', null, XMLDB_NOTNULL, null, null, 'component');
+        $dbman->change_field_default($table, $field);
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000507, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000513) {
+
+        // Define key contextid (foreign) to be added to question_usages
+        $table = new xmldb_table('question_usages');
+        $key = new XMLDBKey('contextid');
+        $key->set_attributes(XMLDB_KEY_FOREIGN, array('contextid'), 'context', array('id'));
+
+        // Launch add key contextid
+        $dbman->add_key($table, $key);
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000513, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000514) {
+
+        // Changing precision of field component on table question_usages to (255)
+        // This was missed during the upgrade from old versions.
+        $table = new xmldb_table('question_usages');
+        $field = new xmldb_field('component');
+        $field->set_attributes(XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, 'contextid');
+
+        // Launch change of precision for field component
+        $dbman->change_field_precision($table, $field);
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000514, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000520) {
+
+        // Define table question_attempts to be created
+        $table = new xmldb_table('question_attempts');
+        if (!table_exists($table)) {
+
+            // Adding fields to table question_attempts
+            $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+            $table->add_field('questionusageid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
+            $table->add_field('slot', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
+            $table->add_field('behaviour', XMLDB_TYPE_CHAR, '32', null, XMLDB_NOTNULL, null, null);
+            $table->add_field('questionid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
+            $table->add_field('maxmark', XMLDB_TYPE_NUMBER, '12, 7', null, XMLDB_NOTNULL, null, null);
+            $table->add_field('minfraction', XMLDB_TYPE_NUMBER, '12, 7', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
+            $table->add_field('flagged', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+            $table->add_field('questionsummary', XMLDB_TYPE_TEXT, 'small', null, null, null, null);
+            $table->add_field('rightanswer', XMLDB_TYPE_TEXT, 'small', null, null, null, null);
+            $table->add_field('responsesummary', XMLDB_TYPE_TEXT, 'small', null, null, null, null);
+            $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
+
+            // Adding keys to table question_attempts
+            $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+            $table->add_key('questionid', XMLDB_KEY_FOREIGN, array('questionid'), 'question', array('id'));
+            $table->add_key('questionusageid', XMLDB_KEY_FOREIGN, array('questionusageid'), 'question_usages', array('id'));
+
+            // Adding indexes to table question_attempts
+            $table->add_index('questionusageid-slot', XMLDB_INDEX_UNIQUE, array('questionusageid', 'slot'));
+
+            // Launch create table for question_attempts
+            $dbman->create_table($table);
+        }
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000520, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000521) {
+
+        // Define table question_attempt_steps to be created
+        $table = new xmldb_table('question_attempt_steps');
+        if (!table_exists($table)) {
+
+            // Adding fields to table question_attempt_steps
+            $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+            $table->add_field('questionattemptid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
+            $table->add_field('sequencenumber', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
+            $table->add_field('state', XMLDB_TYPE_CHAR, '13', null, XMLDB_NOTNULL, null, null);
+            $table->add_field('fraction', XMLDB_TYPE_NUMBER, '12, 7', null, null, null, null);
+            $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
+            $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
+
+            // Adding keys to table question_attempt_steps
+            $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+            $table->add_key('questionattemptid', XMLDB_KEY_FOREIGN, array('questionattemptid'), 'question_attempts_new', array('id'));
+            $table->add_key('userid', XMLDB_KEY_FOREIGN, array('userid'), 'user', array('id'));
+
+            // Adding indexes to table question_attempt_steps
+            $table->add_index('questionattemptid-sequencenumber', XMLDB_INDEX_UNIQUE, array('questionattemptid', 'sequencenumber'));
+
+            // Launch create table for question_attempt_steps
+            $dbman->create_table($table);
+        }
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000521, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000522) {
+
+        // Define table question_attempt_step_data to be created
+        $table = new xmldb_table('question_attempt_step_data');
+        if (!table_exists($table)) {
+
+            // Adding fields to table question_attempt_step_data
+            $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+            $table->add_field('attemptstepid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
+            $table->add_field('name', XMLDB_TYPE_CHAR, '32', null, XMLDB_NOTNULL, null, null);
+            $table->add_field('value', XMLDB_TYPE_TEXT, 'small', null, null, null, null);
+
+            // Adding keys to table question_attempt_step_data
+            $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+            $table->add_key('attemptstepid', XMLDB_KEY_FOREIGN, array('attemptstepid'), 'question_attempt_steps', array('id'));
+
+            // Adding indexes to table question_attempt_step_data
+            $table->add_index('attemptstepid-name', XMLDB_INDEX_UNIQUE, array('attemptstepid', 'name'));
+
+            // Launch create table for question_attempt_step_data
+            $dbman->create_table($table);
+        }
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000522, 'local', 'qedatabase');
+    }
+
+//    if ($oldversion < 2008000550) {
+//        // Define field needsupgradetonewqe to be added to quiz_attempts
+//        $table = new xmldb_table('quiz_attempts');
+//        $field = new xmldb_field('needsupgradetonewqe');
+//        $field->set_attributes(XMLDB_TYPE_INTEGER, '3', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0', 'preview');
+//
+//        // Launch add field needsupgradetonewqe
+//        if (!field_exists($table, $field)) {
+//            $dbman->add_field($table, $field);
+//        }
+//
+//        set_field_select('quiz_attempts', 'needsupgradetonewqe', 1, '', '');
+//
+//        // quiz savepoint reached
+//        upgrade_plugin_savepoint(true, 2008000550, 'local', 'qedatabase');
+//    }
+
+//    if ($oldversion < 2008000551) {
+//        $table = new xmldb_table('question_states');
+//        if (table_exists($table)) {
+//            // First delete all data from preview attempts.
+//            delete_records_select('question_states',
+//                    "attempt IN (SELECT uniqueid FROM {quiz_attempts} WHERE preview = 1)");
+//            delete_records_select('question_sessions',
+//                    "attemptid IN (SELECT uniqueid FROM {quiz_attempts} WHERE preview = 1)");
+//            delete_records('quiz_attempts', 'preview', 1);
+//
+//            // Now update all the old attempt data.
+//            $db->debug = false;
+//            $oldrcachesetting = $CFG->rcache;
+//            $CFG->rcache = false;
+//
+//            require_once($CFG->dirroot . '/question/engine/upgradefromoldqe/upgrade.php');
+//            $upgrader = new question_engine_attempt_upgrader();
+//            $upgrader->convert_all_quiz_attempts();
+//
+//            $CFG->rcache = $oldrcachesetting;
+//            $db->debug = true;
+//        }
+//
+//        // quiz savepoint reached
+//        upgrade_plugin_savepoint(true, 2008000551, 'local', 'qedatabase');
+//    }
+
+    if ($oldversion < 2008000600) {
+
+        // Define table question_hints to be created
+        $table = new xmldb_table('question_hints');
+
+        // Adding fields to table question_hints
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('questionid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
+        $table->add_field('hint', XMLDB_TYPE_TEXT, 'small', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('shownumcorrect', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, null, null, null);
+        $table->add_field('clearwrong', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, null, null, null);
+        $table->add_field('options', XMLDB_TYPE_CHAR, '255', null, null, null, null);
+
+        // Adding keys to table question_hints
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->add_key('questionid', XMLDB_KEY_FOREIGN, array('questionid'), 'question', array('id'));
+
+        // Conditionally launch create table for question_hints
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000600, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000601) {
+
+        // In the past, question_answer fractions were stored with rather
+        // sloppy rounding. Now update them to the new standard of 7 d.p.
+        $changes = array(
+            '-0.66666'  => '-0.6666667',
+            '-0.33333'  => '-0.3333333',
+            '-0.16666'  => '-0.1666667',
+            '-0.142857' => '-0.1428571',
+             '0.11111'  =>  '0.1111111',
+             '0.142857' =>  '0.1428571',
+             '0.16666'  =>  '0.1666667',
+             '0.33333'  =>  '0.3333333',
+             '0.333333' =>  '0.3333333',
+             '0.66666'  =>  '0.6666667',
+        );
+        foreach ($changes as $from => $to) {
+            $DB->set_field('question_answers',
+                    'fraction', $to, array('fraction' => $from));
+        }
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000601, 'local', 'qedatabase');
+    }
+
+    if ($oldversion < 2008000602) {
+
+        // In the past, question penalties were stored with rather
+        // sloppy rounding. Now update them to the new standard of 7 d.p.
+        $DB->set_field('question',
+                'penalty', 0.3333333, array('penalty' => 33.3));
+        $DB->set_field_select('question',
+                'penalty', 0.3333333, 'penalty >= 0.33 AND penalty <= 0.34');
+        $DB->set_field_select('question',
+                'penalty', 0.6666667, 'penalty >= 0.66 AND penalty <= 0.67');
+        $DB->set_field_select('question',
+                'penalty', 1, 'penalty > 1');
+
+        // quiz savepoint reached
+        upgrade_plugin_savepoint(true, 2008000602, 'local', 'qedatabase');
+    }
+}
diff --git a/local/qedatabase/readme.txt b/local/qedatabase/readme.txt
new file mode 100755 (executable)
index 0000000..d77cb90
--- /dev/null
@@ -0,0 +1,2 @@
+This local plugin exists simply to create and update the database as required
+by the new Question Engine.
diff --git a/local/qedatabase/version.php b/local/qedatabase/version.php
new file mode 100755 (executable)
index 0000000..e1ac2db
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Question engine database upgrade version information
+ *
+ * @package    moodlecore
+ * @subpackage questionengine
+ * @copyright  2010 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$plugin->version  = 2008000602;
+$plugin->requires = 2010080300;
index 8b17343..0eba902 100644 (file)
@@ -246,7 +246,7 @@ abstract class question_behaviour {
      * @return string|null a plain text summary of this question.
      */
     public function get_question_summary() {
-        return $this->question->get_question_summary();
+        return $this->question->get_question_summary($this->qa);
     }
 
     /**
@@ -1,9 +1,9 @@
 <?php
 
 $string['assumingcertainty'] = 'You did not select a certainty. Assuming: {$a}.';
-$string['certainty1'] = 'Not very (less than 67%%)';
-$string['certainty2'] = 'Fairly (more than 67%%)';
-$string['certainty3'] = 'Very (more than 85%%)';
+$string['certainty1'] = 'Not very (less than 67%)';
+$string['certainty2'] = 'Fairly (more than 67%)';
+$string['certainty3'] = 'Very (more than 85%)';
 $string['deferredcbm'] = 'Deferred feedback with CBM';
 $string['howcertainareyou'] = 'How certain are you? $a';
 $string['markadjustment'] = 'Based on the certainty you expressed, your base mark of {$a->rawmark} was adjusted to {$a->mark}.';
index de2da4b..11073ea 100644 (file)
@@ -35,7 +35,7 @@
  * @copyright 2009 The Open University
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-abstract class qbehaviour_renderer extends renderer_base {
+abstract class qbehaviour_renderer extends plugin_renderer_base {
     /**
      * Generate some HTML (which may be blank) that appears in the question
      * formulation area, afer the question type generated output.
index e7d49b8..2ef242f 100644 (file)
@@ -1297,6 +1297,9 @@ class question_attempt {
     /** @var integer|string the id of the question_usage_by_activity we belong to. */
     protected $usageid;
 
+    // TODO
+    protected $owningcontextid = null;
+
     /** @var integer the number used to identify this question_attempt within the usage. */
     protected $slot = null;
 
@@ -1779,6 +1782,12 @@ class question_attempt {
         return $this->behaviour->summarise_action($step);
     }
 
+    public function rewrite_pluginfile_urls($text, $component, $filearea) {
+        return question_rewrite_question_urls($text,
+                'pluginfile.php', $this->owningcontextid, $component, $filearea,
+                array($this->get_usage_id(), $this->get_slot()), $this->get_question()->id);
+    }
+
     /**
      * Get the {@link core_question_renderer}, in collaboration with appropriate
      * {@link qbehaviour_renderer} and {@link qtype_renderer} subclasses, to generate the
index b5ab9b0..d6adaed 100644 (file)
@@ -1,3 +1,14 @@
+The magic core commit I backported code from was:
+
+50da63ebd90247819619b80de9d9e40f93e9982b
+
+So
+
+git diff 50da63e..master 
+git diff --stat 50da63e..master 
+
+are useful command stems.
+
 External changes
 ================
 
@@ -168,14 +179,25 @@ DONE question/question.php                              |    3 +-
  question/restorelib.php                            |   88 +-
 DONE question/toggleflag.php                            |   49 +
 
- question/type/edit_question_form.php               |  264 ++-
- question/type/question.html                        |   46 -
- question/type/questionbase.php                     |  787 ++++++
+DONE question/type/edit_question_form.php               |  264 ++-
+DONE question/type/question.html                        |   46 -
+DONE question/type/questionbase.php                     |  787 ++++++
 DONE question/type/questiontype.php                     | 1302 ++-------
- question/type/rendererbase.php                     |  265 ++ -- TODO diff questointype to get necessary changes.
- question/type/simpletest/testquestionbase.php      |  117 +
+DONE question/type/rendererbase.php                     |  265 ++ -- TODO diff questointype to get necessary changes.
+DONE question/type/simpletest/testquestionbase.php      |  117 +
 DONE question/type/simpletest/testquestiontype.php      |   91 +-
 
+DONE question/type/truefalse/db/upgrade.php             |    2 -
+DONE question/type/truefalse/display.html               |   30 -
+DONE question/type/truefalse/edit_truefalse_form.php    |   53 +-
+DONE question/type/truefalse/lang/en_utf8/qtype_truefalse.php     |   14 +
+DONE question/type/truefalse/question.php               |   97 +
+ question/type/truefalse/questiontype.php           |  212 +-
+DONE question/type/truefalse/renderer.php               |  142 +
+DONE question/type/truefalse/simpletest/testquestion.php     |   99 +
+DONE question/type/truefalse/simpletest/testquestiontype.php |   73 +
+DONE question/type/truefalse/version.php                |    4 +-
+
  question/behaviour/behaviourbase.php               |  627 +++++
  question/behaviour/rendererbase.php                |  200 ++
 
@@ -420,17 +442,6 @@ DONE question/type/simpletest/testquestiontype.php      |   91 +-
  question/type/shortanswer/simpletest/testquestiontype.php    |  283 +--
  question/type/shortanswer/version.php              |    4 +-
 
- question/type/truefalse/db/upgrade.php             |    2 -
- question/type/truefalse/display.html               |   30 -
- question/type/truefalse/edit_truefalse_form.php    |   53 +-
- question/type/truefalse/lang/en_utf8/qtype_truefalse.php     |   14 +
- question/type/truefalse/question.php               |   97 +
- question/type/truefalse/questiontype.php           |  212 +-
- question/type/truefalse/renderer.php               |  142 +
- question/type/truefalse/simpletest/testquestion.php     |   99 +
- question/type/truefalse/simpletest/testquestiontype.php |   73 +
- question/type/truefalse/version.php                |    4 +-
-
  theme/standard/styles_color.css                    |   72 +-
  theme/standard/styles_fonts.css                    |   20 +-
  theme/standard/styles_layout.css                   |  265 ++-
index b895cc5..c610ab7 100644 (file)
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
+
 /**
  * A base class for question editing forms.
  *
+ * @package moodlecore
+ * @subpackage questiontypes
  * @copyright &copy; 2006 The Open University
- * @author T.J.Hunt@open.ac.uk
  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package questionbank
- * @subpackage questiontypes
  */
 
+
 /**
  * Form definition base class. This defines the common fields that
  * all question types need. Question types should define their own
  * class that inherits from this one, and implements the definition_inner()
  * method.
  *
- * @package questionbank
- * @subpackage questiontypes
+ * @copyright &copy; 2006 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  */
-class question_edit_form extends moodleform {
+abstract class question_edit_form extends moodleform {
+    const DEFAULT_NUM_HINTS = 2;
+
     /**
      * Question object with options and answers already loaded by get_question_options
      * Be careful how you use this it is needed sometimes to set up the structure of the
      * form in definition_inner but data is always loaded into the form with set_data.
-     *
      * @var object
      */
-    public $question;
-    public $contexts;
-    public $category;
-    public $categorycontext;
+    protected $question;
+
+    protected $contexts;
+    protected $category;
+    protected $categorycontext;
 
     /** @var object current context */
     public $context;
@@ -56,11 +59,10 @@ class question_edit_form extends moodleform {
     /** @var object instance of question type */
     public $instance;
 
-    function question_edit_form($submiturl, $question, $category, $contexts, $formeditable = true){
+    public function __construct($submiturl, $question, $category, $contexts, $formeditable = true) {
         global $DB;
 
         $this->question = $question;
-
         $this->contexts = $contexts;
 
         $record = $DB->get_record('question_categories', array('id' => $question->category), 'contextid');
@@ -72,11 +74,7 @@ class question_edit_form extends moodleform {
         $this->category = $category;
         $this->categorycontext = get_context_instance_by_id($category->contextid);
 
-        if (!empty($question->id)) {
-            $question->id = (int) $question->id;
-        }
-
-        parent::moodleform($submiturl, null, 'post', '', null, $formeditable);
+        parent::__construct($submiturl, null, 'post', '', null, $formeditable);
     }
 
     /**
@@ -86,28 +84,28 @@ class question_edit_form extends moodleform {
      * If your question type does not support all these fields, then you can
      * override this method and remove the ones you don't want with $mform->removeElement().
      */
-    function definition() {
+    public function definition() {
         global $COURSE, $CFG, $DB;
 
         $qtype = $this->qtype();
         $langfile = "qtype_$qtype";
 
-        $mform =& $this->_form;
+        $mform = $this->_form; 
 
         // Standard fields at the start of the form.
         $mform->addElement('header', 'generalheader', get_string("general", 'form'));
 
-        if (!isset($this->question->id)){
+        if (!isset($this->question->id)) {
             // Adding question
-            $mform->addElement('questioncategory', 'category', get_string('category', 'quiz'),
+            $mform->addElement('questioncategory', 'category', get_string('category', 'question'),
                     array('contexts' => $this->contexts->having_cap('moodle/question:add')));
-        } elseif (!($this->question->formoptions->canmove || $this->question->formoptions->cansaveasnew)){
+        } elseif (!($this->question->formoptions->canmove || $this->question->formoptions->cansaveasnew)) {
             // Editing question with no permission to move from category.
-            $mform->addElement('questioncategory', 'category', get_string('category', 'quiz'),
+            $mform->addElement('questioncategory', 'category', get_string('category', 'question'),
                     array('contexts' => array($this->categorycontext)));
-        } elseif ($this->question->formoptions->movecontext){
+        } elseif ($this->question->formoptions->movecontext) {
             // Moving question to another context.
-            $mform->addElement('questioncategory', 'categorymoveto', get_string('category', 'quiz'),
+            $mform->addElement('questioncategory', 'categorymoveto', get_string('category', 'question'),
                     array('contexts' => $this->contexts->having_cap('moodle/question:add')));
 
         } else {
@@ -115,7 +113,7 @@ class question_edit_form extends moodleform {
             $currentgrp = array();
             $currentgrp[0] =& $mform->createElement('questioncategory', 'category', get_string('categorycurrent', 'question'),
                     array('contexts' => array($this->categorycontext)));
-            if ($this->question->formoptions->canedit || $this->question->formoptions->cansaveasnew){
+            if ($this->question->formoptions->canedit || $this->question->formoptions->cansaveasnew) {
                 //not move only form
                 $currentgrp[1] =& $mform->createElement('checkbox', 'usecurrentcat', '', get_string('categorycurrentuse', 'question'));
                 $mform->setDefault('usecurrentcat', 1);
@@ -126,37 +124,30 @@ class question_edit_form extends moodleform {
 
             $mform->addElement('questioncategory', 'categorymoveto', get_string('categorymoveto', 'question'),
                     array('contexts' => array($this->categorycontext)));
-            if ($this->question->formoptions->canedit || $this->question->formoptions->cansaveasnew){
+            if ($this->question->formoptions->canedit || $this->question->formoptions->cansaveasnew) {
                 //not move only form
                 $mform->disabledIf('categorymoveto', 'usecurrentcat', 'checked');
             }
         }
 
-        $mform->addElement('text', 'name', get_string('questionname', 'quiz'), array('size' => 50));
+        $mform->addElement('text', 'name', get_string('questionname', 'question'), array('size' => 50));
         $mform->setType('name', PARAM_TEXT);
         $mform->addRule('name', null, 'required', null, 'client');
 
-        $mform->addElement('editor', 'questiontext', get_string('questiontext', 'quiz'),
+        $mform->addElement('editor', 'questiontext', get_string('questiontext', 'question'),
                 array('rows' => 15), $this->editoroptions);
         $mform->setType('questiontext', PARAM_RAW);
 
-        $mform->addElement('text', 'defaultgrade', get_string('defaultgrade', 'quiz'),
+        $mform->addElement('text', 'defaultmark', get_string('defaultmark', 'question'),
                 array('size' => 3));
-        $mform->setType('defaultgrade', PARAM_INT);
-        $mform->setDefault('defaultgrade', 1);
-        $mform->addRule('defaultgrade', null, 'required', null, 'client');
+        $mform->setType('defaultmark', PARAM_INT);
+        $mform->setDefault('defaultmark', 1);
+        $mform->addRule('defaultmark', null, 'required', null, 'client');
 
-        $mform->addElement('text', 'penalty', get_string('penaltyfactor', 'question'),
-                array('size' => 3));
-        $mform->setType('penalty', PARAM_NUMBER);
-        $mform->addRule('penalty', null, 'required', null, 'client');
-        $mform->addHelpButton('penalty', 'penaltyfactor', 'question');
-        $mform->setDefault('penalty', 0.1);
-
-        $mform->addElement('editor', 'generalfeedback', get_string('generalfeedback', 'quiz'),
+        $mform->addElement('editor', 'generalfeedback', get_string('generalfeedback', 'question'),
                 array('rows' => 10), $this->editoroptions);
         $mform->setType('generalfeedback', PARAM_RAW);
-        $mform->addHelpButton('generalfeedback', 'generalfeedback', 'quiz');
+        $mform->addHelpButton('generalfeedback', 'generalfeedback', 'question');
 
         // Any questiontype specific fields.
         $this->definition_inner($mform);
@@ -166,10 +157,10 @@ class question_edit_form extends moodleform {
             $mform->addElement('tags', 'tags', get_string('tags'));
         }
 
-        if (!empty($this->question->id)){
+        if (!empty($this->question->id)) {
             $mform->addElement('header', 'createdmodifiedheader', get_string('createdmodifiedheader', 'question'));
             $a = new stdClass();
-            if (!empty($this->question->createdby)){
+            if (!empty($this->question->createdby)) {
                 $a->time = userdate($this->question->timecreated);
                 $a->user = fullname($DB->get_record('user', array('id' => $this->question->createdby)));
             } else {
@@ -177,7 +168,7 @@ class question_edit_form extends moodleform {
                 $a->user = get_string('unknown', 'question');
             }
             $mform->addElement('static', 'created', get_string('created', 'question'), get_string('byandon', 'question', $a));
-            if (!empty($this->question->modifiedby)){
+            if (!empty($this->question->modifiedby)) {
                 $a = new stdClass();
                 $a->time = userdate($this->question->timemodified);
                 $a->user = fullname($DB->get_record('user', array('id' => $this->question->modifiedby)));
@@ -218,15 +209,15 @@ class question_edit_form extends moodleform {
         $mform->setDefault('appendqnumstring', 0);
 
         $buttonarray = array();
-        if (!empty($this->question->id)){
+        if (!empty($this->question->id)) {
             //editing / moving question
-            if ($this->question->formoptions->movecontext){
+            if ($this->question->formoptions->movecontext) {
                 $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('moveq', 'question'));
-            } elseif ($this->question->formoptions->canedit || $this->question->formoptions->canmove ||$this->question->formoptions->movecontext){
+            } elseif ($this->question->formoptions->canedit || $this->question->formoptions->canmove ||$this->question->formoptions->movecontext) {
                 $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('savechanges'));
             }
-            if ($this->question->formoptions->cansaveasnew){
-                $buttonarray[] = &$mform->createElement('submit', 'makecopy', get_string('makecopy', 'quiz'));
+            if ($this->question->formoptions->cansaveasnew) {
+                $buttonarray[] = &$mform->createElement('submit', 'makecopy', get_string('makecopy', 'question'));
             }
             $buttonarray[] = &$mform->createElement('cancel');
         } else {
@@ -239,27 +230,17 @@ class question_edit_form extends moodleform {
 
         if ($this->question->formoptions->movecontext) {
             $mform->hardFreezeAllVisibleExcept(array('categorymoveto', 'buttonar'));
-        } else if ((!empty($this->question->id)) && (!($this->question->formoptions->canedit || $this->question->formoptions->cansaveasnew))){
+        } else if ((!empty($this->question->id)) && (!($this->question->formoptions->canedit || $this->question->formoptions->cansaveasnew))) {
             $mform->hardFreezeAllVisibleExcept(array('categorymoveto', 'buttonar', 'currentgrp'));
         }
     }
 
-    function validation($fromform, $files) {
-        $errors = parent::validation($fromform, $files);
-        if (empty($fromform->makecopy) && isset($this->question->id)
-                && ($this->question->formoptions->canedit || $this->question->formoptions->cansaveasnew)
-                && empty($fromform->usecurrentcat) && !$this->question->formoptions->canmove) {
-            $errors['currentgrp'] = get_string('nopermissionmove', 'question');
-        }
-        return $errors;
-    }
-
     /**
      * Add any question-type specific form fields.
      *
      * @param object $mform the form being built.
      */
-    function definition_inner(&$mform) {
+    protected function definition_inner($mform) {
         // By default, do nothing.
     }
 
@@ -272,7 +253,7 @@ class question_edit_form extends moodleform {
      * @param $answersoption reference to return the name of $question->options field holding an array of answers
      * @return array of form fields.
      */
-    function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions, &$answersoption) {
+    protected function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions, &$answersoption) {
         $repeated = array();
         $repeated[] =& $mform->createElement('header', 'answerhdr', $label);
         $repeated[] =& $mform->createElement('text', 'answer', get_string('answer', 'quiz'), array('size' => 80));
@@ -294,17 +275,17 @@ class question_edit_form extends moodleform {
      * @param $minoptions the minimum number of answer blanks to display. Default QUESTION_NUMANS_START.
      * @param $addoptions the number of answer blanks to add. Default QUESTION_NUMANS_ADD.
      */
-    function add_per_answer_fields(&$mform, $label, $gradeoptions, $minoptions = QUESTION_NUMANS_START, $addoptions = QUESTION_NUMANS_ADD) {
+    protected function add_per_answer_fields(&$mform, $label, $gradeoptions, $minoptions = QUESTION_NUMANS_START, $addoptions = QUESTION_NUMANS_ADD) {
         $answersoption = '';
         $repeatedoptions = array();
         $repeated = $this->get_per_answer_fields($mform, $label, $gradeoptions, $repeatedoptions, $answersoption);
 
-        if (isset($this->question->options)){
+        if (isset($this->question->options)) {
             $countanswers = count($this->question->options->$answersoption);
         } else {
             $countanswers = 0;
         }
-        if ($this->question->formoptions->repeatelements){
+        if ($this->question->formoptions->repeatelements) {
             $repeatsatstart = max($minoptions, $countanswers + $addoptions);
         } else {
             $repeatsatstart = $countanswers;
@@ -313,8 +294,90 @@ class question_edit_form extends moodleform {
         $this->repeat_elements($repeated, $repeatsatstart, $repeatedoptions, 'noanswers', 'addanswers', $addoptions, get_string('addmorechoiceblanks', 'qtype_multichoice'));
     }
 
-    function set_data($question) {
+    protected function add_combined_feedback_fields($withshownumpartscorrect = false) {
+        $mform = $this->_form;
+
+        $mform->addElement('header', 'combinedfeedbackhdr', get_string('combinedfeedback', 'question'));
+
+        foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) {
+            $mform->addElement('editor', $feedbackname, get_string($feedbackname, 'question'),
+                                array('rows' => 5), $this->editoroptions);
+            $mform->setType($feedbackname, PARAM_RAW);
+
+            if ($withshownumpartscorrect && $feedbackname == 'partiallycorrectfeedback') {
+                $mform->addElement('checkbox', 'shownumcorrect', get_string('options', 'question'), get_string('shownumpartscorrect', 'question'));
+            }
+        }
+    }
+
+    protected function get_hint_fields($withclearwrong = false, $withshownumpartscorrect = false) {
+        $mform = $this->_form;
+
+        $repeated = array();
+        $repeated[] = $mform->createElement('header', 'answerhdr', get_string('hintn', 'question'));
+        $repeated[] = $mform->createElement('htmleditor', 'hint', get_string('hinttext', 'question'), array('size' => 50));
+        $repeatedoptions['hint']['type'] = PARAM_RAW;
+
+        if ($withclearwrong) {
+            $repeated[] = $mform->createElement('checkbox', 'hintclearwrong', get_string('options', 'question'), get_string('clearwrongparts', 'question'));
+        }
+        if ($withshownumpartscorrect) {
+            $repeated[] = $mform->createElement('checkbox', 'hintshownumcorrect', '', get_string('shownumpartscorrect', 'question'));
+        }
+
+        return array($repeated, $repeatedoptions);
+    }
+
+    protected function add_interactive_settings($withclearwrong = false, $withshownumpartscorrect = false) {
+        $mform = $this->_form;
+
+        $mform->addElement('header', 'multitriesheader', get_string('settingsformultipletries', 'question'));
+
+        $penalties = array(
+            1.0000000,
+            0.5000000,
+            0.3333333,
+            0.2500000,
+            0.2000000,
+            0.1000000,
+            0.0000000
+        );
+        if (!empty($this->question->penalty) && !in_array($this->question->penalty, $penalties)) {
+            $penalties[] = $this->question->penalty;
+            sort($penalties);
+        }
+        $penaltyoptions = array();
+        foreach ($penalties as $penalty) {
+            $penaltyoptions["$penalty"] = (100 * $penalty) . '%';
+        }
+        $mform->addElement('select', 'penalty', get_string('penaltyforeachincorrecttry', 'question'),
+                $penaltyoptions);
+        $mform->addRule('penalty', null, 'required', null, 'client');
+        $mform->setHelpButton('penalty', array('penalty', get_string('penaltyforeachincorrecttry', 'question'), 'question'));
+        $mform->setDefault('penalty', 0.3333333);
+
+        if (isset($this->question->hints)) {
+            $counthints = count($this->question->hints);
+        } else {
+            $counthints = 0;
+        }
+
+        if ($this->question->formoptions->repeatelements) {
+            $repeatsatstart = max(self::DEFAULT_NUM_HINTS, $counthints);
+        } else {
+            $repeatsatstart = $counthints;
+        }
+
+        list($repeated, $repeatedoptions) = $this->get_hint_fields(
+                $withclearwrong, $withshownumpartscorrect);
+        $this->repeat_elements($repeated, $repeatsatstart, $repeatedoptions,
+                'numhints', 'addhint', 1, get_string('addanotherhint', 'question'));
+    }
+
+    public function set_data($question) {
         global $QTYPES;
+        $QTYPES[$question->qtype]->set_default_options($question);
+
         // prepare question text
         $draftid = file_get_submitted_draft_itemid('questiontext');
 
@@ -363,6 +426,17 @@ class question_edit_form extends moodleform {
         }
         // subclass adds data_preprocessing code here
         $question = $this->data_preprocessing($question);
+
+        if (!empty($question->hints)) {
+            $i = 0;
+            foreach ($question->hints as $hint) {
+                $question->hint[$i] = $hint->hint;
+                $question->hintclearwrong[$i] = !empty($hint->clearwrong);
+                $question->hintshownumcorrect[$i] = !empty($hint->shownumcorrect);
+                $i += 1;
+            }
+        }
+
         parent::set_data($question);
     }
 
@@ -372,14 +446,23 @@ class question_edit_form extends moodleform {
      * @param array $question - array to fill in with the default values
      */
     function data_preprocessing($question) {
+        // TODO is this really necessary?
         return $question;
     }
 
+    public function validation($fromform, $files) {
+        $errors = parent::validation($fromform, $files);
+        if (empty($fromform->makecopy) && isset($this->question->id)
+                && ($this->question->formoptions->canedit || $this->question->formoptions->cansaveasnew)
+                && empty($fromform->usecurrentcat) && !$this->question->formoptions->canmove) {
+            $errors['currentgrp'] = get_string('nopermissionmove', 'question');
+        }
+        return $errors;
+    }
+
     /**
      * Override this in the subclass to question type name.
      * @return the question type name, should be the same as the name() method in the question type class.
      */
-    function qtype() {
-        return '';
-    }
+    public abstract function qtype();
 }
index 95eb5ba..a3e0967 100644 (file)
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['editingmissingtype'] = 'Editing a question of an unknown type';
+
+$string['editingmissingtype'] = 'Adding a question of a type that is not installed on this system';
+$string['answerno'] = 'Answer {$a}';
+$string['cannotchangeamissingqtype'] = 'You cannot make any changes to a question of a missing type.';
+$string['deletedquestion'] = 'Deleted question';
+$string['deletedquestiontext'] = 'This question has been deleted. Unable to display anything.';
+$string['editingmissingtype'] = 'Editing a question of a type that is not installed on this system';
+$string['missing'] = 'Question of a type that is not installed on this system';
 $string['missingtype'] = 'Missing type';
+$string['missingqtypewarning'] = 'This question is of a type that is not currently installed on this system. You will not be able to do anything with this question.';
 $string['warningmissingtype'] = '<b>This question is of a type that has not been installed on your Moodle yet.<br />Please alert your Moodle administrator.</b>';
diff --git a/question/type/old_questiontype.php b/question/type/old_questiontype.php
deleted file mode 100644 (file)
index 3c56eb4..0000000
+++ /dev/null
@@ -1,1180 +0,0 @@
-<?php
-
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * The default questiontype class.
- *
- * @author Martin Dougiamas and many others. This has recently been completely
- *         rewritten by Alex Smith, Julian Sedding and Gustav Delius as part of
- *         the Serving Mathematics project
- *         {@link http://maths.york.ac.uk/serving_maths}
- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package questionbank
- * @subpackage questiontypes
- */
-
-require_once($CFG->libdir . '/questionlib.php');
-
-/**
- * This is the base class for Moodle question types.
- *
- * There are detailed comments on each method, explaining what the method is
- * for, and the circumstances under which you might need to override it.
- *
- * Note: the questiontype API should NOT be considered stable yet. Very few
- * question tyeps have been produced yet, so we do not yet know all the places
- * where the current API is insufficient. I would rather learn from the
- * experiences of the first few question type implementors, and improve the
- * interface to meet their needs, rather the freeze the API prematurely and
- * condem everyone to working round a clunky interface for ever afterwards.
- *
- * @package questionbank
- * @subpackage questiontypes
- */
-class default_questiontype {
-    protected $fileoptions = array(
-        'subdirs' => false,
-        'maxfiles' => -1,
-        'maxbytes' => 0,
-    );
-
-    /**
-     * Name of the question type
-     *
-     * The name returned should coincide with the name of the directory
-     * in which this questiontype is located
-     *
-     * @return string the name of this question type.
-     */
-    function name() {
-        return 'default';
-    }
-
-    /**
-     * Returns a list of other question types that this one requires in order to
-     * work. For example, the calculated question type is a subclass of the
-     * numerical question type, which is a subclass of the shortanswer question
-     * type; and the randomsamatch question type requires the shortanswer type
-     * to be installed.
-     *
-     * @return array any other question types that this one relies on. An empty
-     * array if none.
-     */
-    function requires_qtypes() {
-        return array();
-    }
-
-    /**
-     * @return string the name of this pluginfor passing to get_string, set/get_config, etc.
-     */
-    function plugin_name() {
-        return 'qtype_' . $this->name();
-    }
-
-    /**
-     * @return string the name of this question type in the user's language.
-     * You should not need to override this method, the default behaviour should be fine.
-     */
-    function local_name() {
-        return get_string($this->name(), $this->plugin_name());
-    }
-
-    /**
-     * The name this question should appear as in the create new question
-     * dropdown. Override this method to return false if you don't want your
-     * question type to be createable, for example if it is an abstract base type,
-     * otherwise, you should not need to override this method.
-     *
-     * @return mixed the desired string, or false to hide this question type in the menu.
-     */
-    function menu_name() {
-        return $this->local_name();
-    }
-
-    /**
-     * @return boolean override this to return false if this is not really a
-     *      question type, for example the description question type is not
-     *      really a question type.
-     */
-    function is_real_question_type() {
-        return true;
-    }
-
-    /**
-     * @return boolean true if this question type may require manual grading.
-     */
-    function is_manual_graded() {
-        return false;
-    }
-
-    /**
-     * @param object $question a question of this type.
-     * @param string $otherquestionsinuse comma-separate list of other question ids in this attempt.
-     * @return boolean true if a particular instance of this question requires manual grading.
-     */
-    function is_question_manual_graded($question, $otherquestionsinuse) {
-        return $this->is_manual_graded();
-    }
-
-    /**
-     * @return boolean true if a table analyzing responses should be shown in
-     * the quiz statistics report. Usually if a question is manually graded
-     * then this analysis table won't be a good idea.
-     */
-    function show_analysis_of_responses() {
-        return !$this->is_manual_graded();
-    }
-
-    /**
-     * @return boolean true if this question type can be used by the random question type.
-     */
-    function is_usable_by_random() {
-        return true;
-    }
-
-    /**
-     * @param question record.
-     * @param integer subqid this is the id of the subquestion. Usually the id
-     * of the question record of the question record but this is dependent on
-     * the question type. Not relevant to some question types.
-     * @return whether the teacher supplied responses can include wildcards. Can
-     * more than one answer be equivalent to one teacher supplied response.
-     */
-    function has_wildcards_in_responses($question, $subqid) {
-        return false;
-    }
-
-    /**
-     * If your question type has a table that extends the question table, and
-     * you want the base class to automatically save, backup and restore the extra fields,
-     * override this method to return an array wherer the first element is the table name,
-     * and the subsequent entries are the column names (apart from id and questionid).
-     *
-     * @return mixed array as above, or null to tell the base class to do nothing.
-     */
-    function extra_question_fields() {
-        return null;
-    }
-
-    /**
-        * If you use extra_question_fields, overload this function to return question id field name
-        *  in case you table use another name for this column
-        */
-    function questionid_column_name() {
-        return 'questionid';
-    }
-
-    /**
-     * If your question type has a table that extends the question_answers table,
-     * make this method return an array wherer the first element is the table name,
-     * and the subsequent entries are the column names (apart from id and answerid).
-     *
-     * @return mixed array as above, or null to tell the base class to do nothing.
-     */
-    function extra_answer_fields() {
-        return null;
-    }
-
-    /**
-     * Return an instance of the question editing form definition. This looks for a
-     * class called edit_{$this->name()}_question_form in the file
-     * question/type/{$this->name()}/edit_{$this->name()}_question_form.php
-     * and if it exists returns an instance of it.
-     *
-     * @param string $submiturl passed on to the constructor call.
-     * @return object an instance of the form definition, or null if one could not be found.
-     */
-    function create_editing_form($submiturl, $question, $category, $contexts, $formeditable) {
-        global $CFG;
-        require_once("{$CFG->dirroot}/question/type/edit_question_form.php");
-        $definition_file = $CFG->dirroot.'/question/type/'.$this->name().'/edit_'.$this->name().'_form.php';
-        if (!(is_readable($definition_file) && is_file($definition_file))) {
-            return null;
-        }
-        require_once($definition_file);
-        $classname = 'question_edit_'.$this->name().'_form';
-        if (!class_exists($classname)) {
-            return null;
-        }
-        return new $classname($submiturl, $question, $category, $contexts, $formeditable);
-    }
-
-    /**
-     * @return string the full path of the folder this plugin's files live in.
-     */
-    function plugin_dir() {
-        global $CFG;
-        return $CFG->dirroot . '/question/type/' . $this->name();
-    }
-
-    /**
-     * @return string the URL of the folder this plugin's files live in.
-     */
-    function plugin_baseurl() {
-        global $CFG;
-        return $CFG->wwwroot . '/question/type/' . $this->name();
-    }
-
-    /**
-     * This method should be overriden if you want to include a special heading or some other
-     * html on a question editing page besides the question editing form.
-     *
-     * @param question_edit_form $mform a child of question_edit_form
-     * @param object $question
-     * @param string $wizardnow is '' for first page.
-     */
-    function display_question_editing_page(&$mform, $question, $wizardnow){
-        global $OUTPUT;
-        $heading = $this->get_heading(empty($question->id));
-
-        echo $OUTPUT->heading_with_help($heading, $this->name(), $this->plugin_name());
-
-        $permissionstrs = array();
-        if (!empty($question->id)){
-            if ($question->formoptions->canedit){
-                $permissionstrs[] = get_string('permissionedit', 'question');
-            }
-            if ($question->formoptions->canmove){
-                $permissionstrs[] = get_string('permissionmove', 'question');
-            }
-            if ($question->formoptions->cansaveasnew){
-                $permissionstrs[] = get_string('permissionsaveasnew', 'question');
-            }
-        }
-        if (!$question->formoptions->movecontext  && count($permissionstrs)){
-            echo $OUTPUT->heading(get_string('permissionto', 'question'), 3);
-            $html = '<ul>';
-            foreach ($permissionstrs as $permissionstr){
-                $html .= '<li>'.$permissionstr.'</li>';
-            }
-            $html .= '</ul>';
-            echo $OUTPUT->box($html, 'boxwidthnarrow boxaligncenter generalbox');
-        }
-        $mform->display();
-    }
-
-    /**
-     * Method called by display_question_editing_page and by question.php to get heading for breadcrumbs.
-     *
-     * @return array a string heading and the langmodule in which it was found.
-     */
-    function get_heading($adding = false){
-        if ($adding) {
-            $prefix = 'adding';
-        } else {
-            $prefix = 'editing';
-        }
-        return get_string($prefix . $this->name(), $this->plugin_name());
-    }
-
-    /**
-    * Saves (creates or updates) a question.
-    *
-    * Given some question info and some data about the answers
-    * this function parses, organises and saves the question
-    * It is used by {@link question.php} when saving new data from
-    * a form, and also by {@link import.php} when importing questions
-    * This function in turn calls {@link save_question_options}
-    * to save question-type specific data.
-    *
-    * Whether we are saving a new question or updating an existing one can be
-    * determined by testing !empty($question->id). If it is not empty, we are updating.
-    *
-    * The question will be saved in category $form->category.
-    *
-    * @param object $question the question object which should be updated. For a new question will be mostly empty.
-    * @param object $form the object containing the information to save, as if from the question editing form.
-    * @param object $course not really used any more.
-    * @return object On success, return the new question object. On failure,
-    *       return an object as follows. If the error object has an errors field,
-    *       display that as an error message. Otherwise, the editing form will be
-    *       redisplayed with validation errors, from validation_errors field, which
-    *       is itself an object, shown next to the form fields. (I don't think this is accurate any more.)
-    */
-    function save_question($question, $form) {
-        global $USER, $DB, $OUTPUT;
-
-        list($question->category) = explode(',', $form->category);
-        $context = $this->get_context_by_category_id($question->category);
-
-        // This default implementation is suitable for most
-        // question types.
-
-        // First, save the basic question itself
-        $question->name = trim($form->name);
-        $question->parent = isset($form->parent) ? $form->parent : 0;
-        $question->length = $this->actual_number_of_questions($question);
-        $question->penalty = isset($form->penalty) ? $form->penalty : 0;
-
-        if (empty($form->questiontext['text'])) {
-            $question->questiontext = '';
-        } else {
-            $question->questiontext = trim($form->questiontext['text']);;
-        }
-        $question->questiontextformat = !empty($form->questiontext['format'])?$form->questiontext['format']:0;
-
-        if (empty($form->generalfeedback['text'])) {
-            $question->generalfeedback = '';
-        } else {
-            $question->generalfeedback = trim($form->generalfeedback['text']);
-        }
-        $question->generalfeedbackformat = !empty($form->generalfeedback['format'])?$form->generalfeedback['format']:0;
-
-        if (empty($question->name)) {
-            $question->name = shorten_text(strip_tags($form->questiontext['text']), 15);
-            if (empty($question->name)) {
-                $question->name = '-';
-            }
-        }
-
-        if ($question->penalty > 1 or $question->penalty < 0) {
-            $question->errors['penalty'] = get_string('invalidpenalty', 'quiz');
-        }
-
-        if (isset($form->defaultgrade)) {
-            $question->defaultgrade = $form->defaultgrade;
-        }
-
-        // If the question is new, create it.
-        if (empty($question->id)) {
-            // Set the unique code
-            $question->stamp = make_unique_id_code();
-            $question->createdby = $USER->id;
-            $question->timecreated = time();
-            $question->id = $DB->insert_record('question', $question);
-        }
-
-        // Now, whether we are updating a existing question, or creating a new
-        // one, we have to do the files processing and update the record.
-        /// Question already exists, update.
-        $question->modifiedby = $USER->id;
-        $question->timemodified = time();
-
-        if (!empty($question->questiontext) && !empty($form->questiontext['itemid'])) {
-            $question->questiontext = file_save_draft_area_files($form->questiontext['itemid'], $context->id, 'question', 'questiontext', (int)$question->id, $this->fileoptions, $question->questiontext);
-        }
-        if (!empty($question->generalfeedback) && !empty($form->generalfeedback['itemid'])) {
-            $question->generalfeedback = file_save_draft_area_files($form->generalfeedback['itemid'], $context->id, 'question', 'generalfeedback', (int)$question->id, $this->fileoptions, $question->generalfeedback);
-        }
-        $DB->update_record('question', $question);
-
-        // Now to save all the answers and type-specific options
-        $form->id = $question->id;
-        $form->qtype = $question->qtype;
-        $form->category = $question->category;
-        $form->questiontext = $question->questiontext;
-        $form->questiontextformat = $question->questiontextformat;
-        // current context
-        $form->context = $context;
-
-        $result = $this->save_question_options($form);
-
-        if (!empty($result->error)) {
-            print_error($result->error);
-        }
-
-        if (!empty($result->notice)) {
-            notice($result->notice, "question.php?id=$question->id");
-        }
-
-        if (!empty($result->noticeyesno)) {
-            throw new coding_exception('$result->noticeyesno no longer supported in save_question.');
-        }
-
-        // Give the question a unique version stamp determined by question_hash()
-        $DB->set_field('question', 'version', question_hash($question), array('id' => $question->id));
-
-        return $question;
-    }
-
-    /**
-     * Saves question-type specific options
-     *
-     * This is called by {@link save_question()} to save the question-type specific data
-     * @return object $result->error or $result->noticeyesno or $result->notice
-     * @param object $question  This holds the information from the editing form,
-     *      it is not a standard question object.
-     */
-    function save_question_options($question) {
-        global $DB;
-        $extra_question_fields = $this->extra_question_fields();
-
-        if (is_array($extra_question_fields)) {
-            $question_extension_table = array_shift($extra_question_fields);
-
-            $function = 'update_record';
-            $questionidcolname = $this->questionid_column_name();
-            $options = $DB->get_record($question_extension_table, array($questionidcolname => $question->id));
-            if (!$options) {
-                $function = 'insert_record';
-                $options = new stdClass;
-                $options->$questionidcolname = $question->id;
-            }
-            foreach ($extra_question_fields as $field) {
-                if (!isset($question->$field)) {
-                    $result = new stdClass;
-                    $result->error = "No data for field $field when saving " .
-                            $this->name() . ' question id ' . $question->id;
-                    return $result;
-                }
-                $options->$field = $question->$field;
-            }
-
-            if (!$DB->{$function}($question_extension_table, $options)) {
-                $result = new stdClass;
-                $result->error = 'Could not save question options for ' .
-                        $this->name() . ' question id ' . $question->id;
-                return $result;
-            }
-        }
-
-        $extra_answer_fields = $this->extra_answer_fields();
-        // TODO save the answers, with any extra data.
-
-        return null;
-    }
-
-    /**
-     * Loads the question type specific options for the question.
-     *
-     * This function loads any question type specific options for the
-     * question from the database into the question object. This information
-     * is placed in the $question->options field. A question type is
-     * free, however, to decide on a internal structure of the options field.
-     * @return bool            Indicates success or failure.
-     * @param object $question The question object for the question. This object
-     *                         should be updated to include the question type
-     *                         specific information (it is passed by reference).
-     */
-    function get_question_options(&$question) {
-        global $CFG, $DB, $OUTPUT;
-
-        if (!isset($question->options)) {
-            $question->options = new stdClass();
-        }
-
-        $extra_question_fields = $this->extra_question_fields();
-        if (is_array($extra_question_fields)) {
-            $question_extension_table = array_shift($extra_question_fields);
-            $extra_data = $DB->get_record($question_extension_table, array($this->questionid_column_name() => $question->id), implode(', ', $extra_question_fields));
-            if ($extra_data) {
-                foreach ($extra_question_fields as $field) {
-                    $question->options->$field = $extra_data->$field;
-                }
-            } else {
-                echo $OUTPUT->notification("Failed to load question options from the table $question_extension_table for questionid " .
-                        $question->id);
-                return false;
-            }
-        }
-
-        $extra_answer_fields = $this->extra_answer_fields();
-        if (is_array($extra_answer_fields)) {
-            $answer_extension_table = array_shift($extra_answer_fields);
-            $question->options->answers = $DB->get_records_sql("
-                    SELECT qa.*, qax." . implode(', qax.', $extra_answer_fields) . "
-                    FROM {question_answers} qa, {$answer_extension_table} qax
-                    WHERE qa.questionid = ? AND qax.answerid = qa.id", array($question->id));
-            if (!$question->options->answers) {
-                echo $OUTPUT->notification("Failed to load question answers from the table $answer_extension_table for questionid " .
-                        $question->id);
-                return false;
-            }
-        } else {
-            // Don't check for success or failure because some question types do not use the answers table.
-            $question->options->answers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC');
-        }
-
-        return true;
-    }
-
-    /**
-    * Deletes states from the question-type specific tables
-    *
-    * @param string $stateslist  Comma separated list of state ids to be deleted
-    */
-    function delete_states($stateslist) {
-        /// The default question type does not have any tables of its own
-        // therefore there is nothing to delete
-
-        return true;
-    }
-
-    /**
-     * Deletes the question-type specific data when a question is deleted.
-     * @param integer $question the question being deleted.
-     * @param integer $contextid the context this quesiotn belongs to.
-     */
-    function delete_question($questionid, $contextid) {
-        global $DB;
-
-        $this->delete_files($questionid, $contextid);
-
-        $extra_question_fields = $this->extra_question_fields();
-        if (is_array($extra_question_fields)) {
-            $question_extension_table = array_shift($extra_question_fields);
-            $DB->delete_records($question_extension_table,
-                    array($this->questionid_column_name() => $questionid));
-        }
-
-        $extra_answer_fields = $this->extra_answer_fields();
-        if (is_array($extra_answer_fields)) {
-            $answer_extension_table = array_shift($extra_answer_fields);
-            $DB->delete_records_select($answer_extension_table,
-                "answerid IN (SELECT qa.id FROM {question_answers} qa WHERE qa.question = ?)", array($questionid));
-        }
-
-        $DB->delete_records('question_answers', array('question' => $questionid));
-    }
-
-    /**
-    * Returns the number of question numbers which are used by the question
-    *
-    * This function returns the number of question numbers to be assigned
-    * to the question. Most question types will have length one; they will be
-    * assigned one number. The 'description' type, however does not use up a
-    * number and so has a length of zero. Other question types may wish to
-    * handle a bundle of questions and hence return a number greater than one.
-    * @return integer         The number of question numbers which should be
-    *                         assigned to the question.
-    * @param object $question The question whose length is to be determined.
-    *                         Question type specific information is included.
-    */
-    function actual_number_of_questions($question) {
-        // By default, each question is given one number
-        return 1;
-    }
-
-    /**
-    * Creates empty session and response information for the question
-    *
-    * This function is called to start a question session. Empty question type
-    * specific session data (if any) and empty response data will be added to the
-    * state object. Session data is any data which must persist throughout the
-    * attempt possibly with updates as the user interacts with the
-    * question. This function does NOT create new entries in the database for
-    * the session; a call to the {@link save_session_and_responses} member will
-    * occur to do this.
-    * @return bool            Indicates success or failure.
-    * @param object $question The question for which the session is to be
-    *                         created. Question type specific information is
-    *                         included.
-    * @param object $state    The state to create the session for. Note that
-    *                         this will not have been saved in the database so
-    *                         there will be no id. This object will be updated
-    *                         to include the question type specific information
-    *                         (it is passed by reference). In particular, empty
-    *                         responses will be created in the ->responses
-    *                         field.
-    * @param object $cmoptions
-    * @param object $attempt  The attempt for which the session is to be
-    *                         started. Questions may wish to initialize the
-    *                         session in different ways depending on the user id
-    *                         or time available for the attempt.
-    */
-    function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
-        // The default implementation should work for the legacy question types.
-        // Most question types with only a single form field for the student's response
-        // will use the empty string '' as the index for that one response. This will
-        // automatically be stored in and restored from the answer field in the
-        // question_states table.
-        $state->responses = array(
-                '' => '',
-        );
-        return true;
-    }
-
-    /**
-    * Restores the session data and most recent responses for the given state
-    *
-    * This function loads any session data associated with the question
-    * session in the given state from the database into the state object.
-    * In particular it loads the responses that have been saved for the given
-    * state into the ->responses member of the state object.
-    *
-    * Question types with only a single form field for the student's response
-    * will not need not restore the responses; the value of the answer
-    * field in the question_states table is restored to ->responses['']
-    * before this function is called. Question types with more response fields
-    * should override this method and set the ->responses field to an
-    * associative array of responses.
-    * @return bool            Indicates success or failure.
-    * @param object $question The question object for the question including any
-    *                         question type specific information.
-    * @param object $state    The saved state to load the session for. This
-    *                         object should be updated to include the question
-    *                         type specific session information and responses
-    *                         (it is passed by reference).
-    */
-    function restore_session_and_responses(&$question, &$state) {
-        // The default implementation does nothing (successfully)
-        return true;
-    }
-
-    /**
-    * Saves the session data and responses for the given question and state
-    *
-    * This function saves the question type specific session data from the
-    * state object to the database. In particular for most question types it saves the
-    * responses from the ->responses member of the state object. The question type
-    * non-specific data for the state has already been saved in the question_states
-    * table and the state object contains the corresponding id and
-    * sequence number which may be used to index a question type specific table.
-    *
-    * Question types with only a single form field for the student's response
-    * which is contained in ->responses[''] will not have to save this response,
-    * it will already have been saved to the answer field of the question_states table.
-    * Question types with more response fields should override this method to convert
-    * the data the ->responses array into a single string field, and save it in the
-    * database. The implementation in the multichoice question type is a good model to follow.
-    * http://cvs.moodle.org/contrib/plugins/question/type/opaque/questiontype.php?view=markup
-    * has a solution that is probably quite generally applicable.
-    * @return bool            Indicates success or failure.
-    * @param object $question The question object for the question including
-    *                         the question type specific information.
-    * @param object $state    The state for which the question type specific
-    *                         data and responses should be saved.
-    */
-    function save_session_and_responses(&$question, &$state) {
-        // The default implementation does nothing (successfully)
-        return true;
-    }
-
-    /**
-    * Returns an array of values which will give full marks if graded as
-    * the $state->responses field
-    *
-    * The correct answer to the question in the given state, or an example of
-    * a correct answer if there are many, is returned. This is used by some question
-    * types in the {@link grade_responses()} function but it is also used by the
-    * question preview screen to fill in correct responses.
-    * @return mixed           A response array giving the responses corresponding
-    *                         to the (or a) correct answer to the question. If there is
-    *                         no correct answer that scores 100% then null is returned.
-    * @param object $question The question for which the correct answer is to
-    *                         be retrieved. Question type specific information is
-    *                         available.
-    * @param object $state    The state of the question, for which a correct answer is
-    *                         needed. Question type specific information is included.
-    */
-    function get_correct_responses(&$question, &$state) {
-        /* The default implementation returns the response for the first answer
-        that gives full marks. */
-        if ($question->options->answers) {
-            foreach ($question->options->answers as $answer) {
-                if (((int) $answer->fraction) === 1) {
-                    return array('' => $answer->answer);
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-    * Return an array of values with the texts for all possible responses stored
-    * for the question
-    *
-    * All answers are found and their text values isolated
-    * @return object          A mixed object
-    *             ->id        question id. Needed to manage random questions:
-    *                         it's the id of the actual question presented to user in a given attempt
-    *             ->responses An array of values giving the responses corresponding
-    *                         to all answers to the question. Answer ids are used as keys.
-    *                         The text and partial credit are the object components
-    * @param object $question The question for which the answers are to
-    *                         be retrieved. Question type specific information is
-    *                         available.
-    */
-    // ULPGC ecastro
-    function get_all_responses(&$question, &$state) {
-        if (isset($question->options->answers) && is_array($question->options->answers)) {
-            $answers = array();
-            foreach ($question->options->answers as $aid=>$answer) {
-                $r = new stdClass;
-                $r->answer = $answer->answer;
-                $r->credit = $answer->fraction;
-                $answers[$aid] = $r;
-            }
-            $result = new stdClass;
-            $result->id = $question->id;
-            $result->responses = $answers;
-            return $result;
-        } else {
-            return null;
-        }
-    }
-    /**
-     * The difference between this method an get_all_responses is that this
-     * method is not passed a state object. It is the possible answers to a
-     * question no matter what the state.
-     * This method is not called for random questions.
-     * @return array of possible answers.
-     */
-    function get_possible_responses(&$question) {
-        static $responses = array();
-        if (!isset($responses[$question->id])){
-            $responses[$question->id] = $this->get_all_responses($question, new object());
-        }
-        return array($question->id => $responses[$question->id]->responses);
-    }
-
-    /**
-     * @param object $question
-     * @return mixed either a integer score out of 1 that the average random
-     * guess by a student might give or an empty string which means will not
-     * calculate.
-     */
-    function get_random_guess_score($question) {
-        return 0;
-    }
-   /**
-    * Return the actual response to the question in a given state
-    * for the question. Text is not yet formatted for output.
-    *
-    * @return mixed           An array containing the response or reponses (multiple answer, match)
-    *                         given by the user in a particular attempt.
-    * @param object $question The question for which the correct answer is to
-    *                         be retrieved. Question type specific information is
-    *                         available.
-    * @param object $state    The state object that corresponds to the question,
-    *                         for which a correct answer is needed. Question
-    *                         type specific information is included.
-    */
-    // ULPGC ecastro
-    function get_actual_response($question, $state) {
-       if (!empty($state->responses)) {
-           $responses[] = $state->responses[''];
-       } else {
-           $responses[] = '';
-       }
-       return $responses;
-    }
-
-    function get_actual_response_details($question, $state) {
-        $response = array_shift($this->get_actual_response($question, $state));
-        $teacherresponses = $this->get_possible_responses($question, $state);
-        //only one response
-        list($tsubqid, $tresponses) = each($teacherresponses);
-        $responsedetail = new stdClass();
-        $responsedetail->subqid = $tsubqid;
-        $responsedetail->response = $response;
-        if ($aid = $this->check_response($question, $state)){
-            $responsedetail->aid = $aid;
-        } else {
-            foreach ($tresponses as $aid => $tresponse){
-                if ($tresponse->answer == $response){
-                    $responsedetail->aid = $aid;
-                    break;
-                }
-            }
-        }
-        if (isset($responsedetail->aid)){
-            $responsedetail->credit = $tresponses[$aid]->credit;
-        } else {
-            $responsedetail->aid = 0;
-            $responsedetail->credit = 0;
-        }
-        return array($responsedetail);
-    }
-
-    // ULPGC ecastro
-    function get_fractional_grade(&$question, &$state) {
-        $grade = $state->grade;
-        if ($question->maxgrade > 0) {
-            return (float)($grade / $question->maxgrade);
-        } else {
-            return (float)$grade;
-        }
-    }
-
-
-    /**
-    * Checks if the response given is correct and returns the id
-    *
-    * @return int             The ide number for the stored answer that matches the response
-    *                         given by the user in a particular attempt.
-    * @param object $question The question for which the correct answer is to
-    *                         be retrieved. Question type specific information is
-    *                         available.
-    * @param object $state    The state object that corresponds to the question,
-    *                         for which a correct answer is needed. Question
-    *                         type specific information is included.
-    */
-    // ULPGC ecastro
-    function check_response(&$question, &$state){
-        return false;
-    }
-
-    // Used by the following function, so that it only returns results once per quiz page.
-    private $htmlheadalreadydone = false;
-    /**
-     * Hook to allow question types to include required JavaScrip or CSS on pages
-     * where they are going to be printed.
-     *
-     * If this question type requires JavaScript to function,
-     * then this method, which will be called before print_header on any page
-     * where this question is going to be printed, is a chance to call
-     * $PAGE->requires->js, and so on.
-     *
-     * The two parameters match the first two parameters of print_question.
-     *
-     * @param object $question The question object.
-     * @param object $state    The state object.
-     */
-    function get_html_head_contributions(&$question, &$state) {
-        // We only do this once for this question type, no matter how often this
-        // method is called on one page.
-        if ($this->htmlheadalreadydone) {
-            return;
-        }
-        $this->htmlheadalreadydone = true;
-
-        // By default, we link to any of the files script.js or script.php that
-        // exist in the plugin folder.
-        $this->find_standard_scripts();
-    }
-
-    /**
-     * Like @see{get_html_head_contributions}, but this method is for CSS and
-     * JavaScript required on the question editing page question/question.php.
-     */
-    function get_editing_head_contributions() {
-        // By default, we link to any of the files styles.css, styles.php,
-        // script.js or script.php that exist in the plugin folder.
-        // Core question types should not use this mechanism. Their styles
-        // should be included in the standard theme.
-        $this->find_standard_scripts();
-    }
-
-    /**
-     * Utility method used by @see{get_html_head_contributions} and
-     * @see{get_editing_head_contributions}. This looks for any of the files
-     * script.js or script.php that exist in the plugin folder and ensures they
-     * get included.
-     */
-    protected function find_standard_scripts() {
-        global $PAGE;
-
-        $plugindir = $this->plugin_dir();
-        $plugindirrel = 'question/type/' . $this->name();
-
-        if (file_exists($plugindir . '/script.js')) {
-            $PAGE->requires->js('/' . $plugindirrel . '/script.js');
-        }
-        if (file_exists($plugindir . '/script.php')) {
-            $PAGE->requires->js('/' . $plugindirrel . '/script.php');
-        }
-    }
-
-    /**
-    * Returns true if the editing wizard is finished, false otherwise.
-    *
-    * The default implementation returns true, which is suitable for all question-
-    * types that only use one editing form. This function is used in
-    * question.php to decide whether we can regrade any states of the edited
-    * question and redirect to edit.php.
-    *
-    * The dataset dependent question-type, which is extended by the calculated
-    * question-type, overwrites this method because it uses multiple pages (i.e.
-    * a wizard) to set up the question and associated datasets.
-    *
-    * @param object $form  The data submitted by the previous page.
-    *
-    * @return boolean      Whether the wizard's last page was submitted or not.
-    */
-    function finished_edit_wizard(&$form) {
-        //In the default case there is only one edit page.
-        return true;
-    }
-
-    /**
-     * Call format_text from weblib.php with the options appropriate to question types.
-     *
-     * @param string $text the text to format.
-     * @param integer $text the type of text. Normally $question->questiontextformat.
-     * @param object $cmoptions the context the string is being displayed in. Only $cmoptions->course is used.
-     * @return string the formatted text.
-     */
-    function format_text($text, $textformat, $cmoptions = NULL) {
-        $formatoptions = new stdClass;
-        $formatoptions->noclean = true;
-        $formatoptions->para = false;
-        return format_text($text, $textformat, $formatoptions, $cmoptions === NULL ? NULL : $cmoptions->course);
-    }
-
-    /**
-     * @return the best link to pass to print_error.
-     * @param $cmoptions as passed in from outside.
-     */
-    function error_link($cmoptions) {
-        global $CFG;
-        $cm = get_coursemodule_from_instance('quiz', $cmoptions->id);
-        if (!empty($cm->id)) {
-            return $CFG->wwwroot . '/mod/quiz/view.php?id=' . $cm->id;
-        } else if (!empty($cm->course)) {
-            return $CFG->wwwroot . '/course/view.php?id=' . $cm->course;
-        } else {
-            return '';
-        }
-    }
-
-/// IMPORT/EXPORT FUNCTIONS /////////////////
-
-    /*
-     * Imports question from the Moodle XML format
-     *
-     * Imports question using information from extra_question_fields function
-     * If some of you fields contains id's you'll need to reimplement this
-     */
-    function import_from_xml($data, $question, $format, $extra=null) {
-        $question_type = $data['@']['type'];
-        if ($question_type != $this->name()) {
-            return false;
-        }
-
-        $extraquestionfields = $this->extra_question_fields();
-        if (!is_array($extraquestionfields)) {
-            return false;
-        }
-
-        //omit table name
-        array_shift($extraquestionfields);
-        $qo = $format->import_headers($data);
-        $qo->qtype = $question_type;
-
-        foreach ($extraquestionfields as $field) {
-            $qo->$field = $format->getpath($data, array('#',$field,0,'#'), $qo->$field);
-        }
-
-        // run through the answers
-        $answers = $data['#']['answer'];
-        $a_count = 0;
-        $extraasnwersfields = $this->extra_answer_fields();
-        if (is_array($extraasnwersfields)) {
-            //TODO import the answers, with any extra data.
-        } else {
-            foreach ($answers as $answer) {
-                $ans = $format->import_answer($answer);
-                $qo->answer[$a_count] = $ans->answer;
-                $qo->fraction[$a_count] = $ans->fraction;
-                $qo->feedback[$a_count] = $ans->feedback;
-                ++$a_count;
-            }
-        }
-        return $qo;
-    }
-
-    /*
-     * Export question to the Moodle XML format
-     *
-     * Export question using information from extra_question_fields function
-     * If some of you fields contains id's you'll need to reimplement this
-     */
-    function export_to_xml($question, $format, $extra=null) {
-        $extraquestionfields = $this->extra_question_fields();
-        if (!is_array($extraquestionfields)) {
-            return false;
-        }
-
-        //omit table name
-        array_shift($extraquestionfields);
-        $expout='';
-        foreach ($extraquestionfields as $field) {
-            $exportedvalue = $question->options->$field;
-            if (!empty($exportedvalue) && htmlspecialchars($exportedvalue) != $exportedvalue) {
-                $exportedvalue = '<![CDATA[' . $exportedvalue . ']]>';
-            }
-            $expout .= "    <$field>{$exportedvalue}</$field>\n";
-        }
-
-        $extraasnwersfields = $this->extra_answer_fields();
-        if (is_array($extraasnwersfields)) {
-            //TODO export answers with any extra data
-        } else {
-            foreach ($question->options->answers as $answer) {
-                $percent = 100 * $answer->fraction;
-                $expout .= "    <answer fraction=\"$percent\">\n";
-                $expout .= $format->writetext($answer->answer, 3, false);
-                $expout .= "      <feedback>\n";
-                $expout .= $format->writetext($answer->feedback, 4, false);
-                $expout .= "      </feedback>\n";
-                $expout .= "    </answer>\n";
-            }
-        }
-        return $expout;
-    }
-
-    /**
-     * Abstract function implemented by each question type. It runs all the code
-     * required to set up and save a question of any type for testing purposes.
-     * Alternate DB table prefix may be used to facilitate data deletion.
-     */
-    function generate_test($name, $courseid=null) {
-        $form = new stdClass();
-        $form->name = $name;
-        $form->questiontextformat = 1;
-        $form->questiontext = 'test question, generated by script';
-        $form->defaultgrade = 1;
-        $form->penalty = 0.1;
-        $form->generalfeedback = "Well done";
-
-        $context = get_context_instance(CONTEXT_COURSE, $courseid);
-        $newcategory = question_make_default_categories(array($context));
-        $form->category = $newcategory->id . ',1';
-
-        $question = new stdClass();
-        $question->courseid = $courseid;
-        $question->qtype = $this->qtype;
-        return array($form, $question);
-    }
-
-    /**
-     * Get question context by category id
-     * @param int $category
-     * @return object $context
-     */
-    function get_context_by_category_id($category) {
-        global $DB;
-        $contextid = $DB->get_field('question_categories', 'contextid', array('id'=>$category));
-        $context = get_context_instance_by_id($contextid);
-        return $context;
-    }
-
-    /**
-     * Save the file belonging to one text field.
-     *
-     * @param array $field the data from the form (or from import). This will
-     *      normally have come from the formslib editor element, so it will be an
-     *      array with keys 'text', 'format' and 'itemid'. However, when we are
-     *      importing, it will be an array with keys 'text', 'format' and 'files'
-     * @param object $context the context the question is in.
-     * @param string $component indentifies the file area question.
-     * @param string $filearea indentifies the file area questiontext, generalfeedback,answerfeedback.
-     * @param integer $itemid identifies the file area.
-     *
-     * @return string the text for this field, after files have been processed.
-     */
-    protected function import_or_save_files($field, $context, $component, $filearea, $itemid) {
-        if (!empty($field['itemid'])) {
-            // This is the normal case. We are safing the questions editing form.
-            return file_save_draft_area_files($field['itemid'], $context->id, $component,
-                    $filearea, $itemid, $this->fileoptions, trim($field['text']));
-
-        } else if (!empty($field['files'])) {
-            // This is the case when we are doing an import.
-            foreach ($field['files'] as $file) {
-                $this->import_file($context, $component,  $filearea, $itemid, $file);
-            }
-        }
-        return trim($field['text']);
-    }
-
-    /**
-     * Move all the files belonging to this question from one context to another.
-     * @param integer $questionid the question being moved.
-     * @param integer $oldcontextid the context it is moving from.
-     * @param integer $newcontextid the context it is moving to.
-     */
-    public function move_files($questionid, $oldcontextid, $newcontextid) {
-        $fs = get_file_storage();
-        $fs->move_area_files_to_new_context($oldcontextid,
-                $newcontextid, 'question', 'questiontext', $questionid);
-        $fs->move_area_files_to_new_context($oldcontextid,
-                $newcontextid, 'question', 'generalfeedback', $questionid);
-    }
-
-    /**
-     * Move all the files belonging to this question's answers when the question
-     * is moved from one context to another.
-     * @param integer $questionid the question being moved.
-     * @param integer $oldcontextid the context it is moving from.
-     * @param integer $newcontextid the context it is moving to.
-     * @param boolean $answerstoo whether there is an 'answer' question area,
-     *      as well as an 'answerfeedback' one. Default false.
-     */
-    protected function move_files_in_answers($questionid, $oldcontextid, $newcontextid, $answerstoo = false) {
-        global $DB;
-        $fs = get_file_storage();
-
-        $answerids = $DB->get_records_menu('question_answers',
-                array('question' => $questionid), 'id', 'id,1');
-        foreach ($answerids as $answerid => $notused) {
-            if ($answerstoo) {
-                $fs->move_area_files_to_new_context($oldcontextid,
-                        $newcontextid, 'question', 'answer', $answerid);
-            }
-            $fs->move_area_files_to_new_context($oldcontextid,
-                    $newcontextid, 'question', 'answerfeedback', $answerid);
-        }
-    }
-
-    /**
-     * Delete all the files belonging to this question.
-     * @param integer $questionid the question being deleted.
-     * @param integer $contextid the context the question is in.
-     */
-    protected function delete_files($questionid, $contextid) {
-        $fs = get_file_storage();
-        $fs->delete_area_files($contextid, 'question', 'questiontext', $questionid);
-        $fs->delete_area_files($contextid, 'question', 'generalfeedback', $questionid);
-    }
-
-    /**
-     * Delete all the files belonging to this question's answers.
-     * @param integer $questionid the question being deleted.
-     * @param integer $contextid the context the question is in.
-     * @param boolean $answerstoo whether there is an 'answer' question area,
-     *      as well as an 'answerfeedback' one. Default false.
-     */
-    protected function delete_files_in_answers($questionid, $contextid, $answerstoo = false) {
-        global $DB;
-        $fs = get_file_storage();
-
-        $answerids = $DB->get_records_menu('question_answers',
-                array('question' => $questionid), 'id', 'id,1');
-        foreach ($answerids as $answerid => $notused) {
-            if ($answerstoo) {
-                $fs->delete_area_files($contextid, 'question', 'answer', $answerid);
-            }
-            $fs->delete_area_files($contextid, 'question', 'answerfeedback', $answerid);
-        }
-    }
-
-    function import_file($context, $component, $filearea, $itemid, $file) {
-        $fs = get_file_storage();
-        $record = new stdclass;
-        if (is_object($context)) {
-            $record->contextid = $context->id;
-        } else {
-            $record->contextid = $context;
-        }
-        $record->component = $component;
-        $record->filearea  = $filearea;
-        $record->itemid    = $itemid;
-        $record->filename  = $file->name;
-        $record->filepath  = '/';
-        return $fs->create_file_from_string($record, $this->decode_file($file));
-    }
-
-    function decode_file($file) {
-        switch ($file->encoding) {
-        case 'base64':
-        default:
-            return base64_decode($file->content);
-        }
-    }
-}
diff --git a/question/type/question.html b/question/type/question.html
deleted file mode 100644 (file)
index f59ebe8..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-/* This template determines the overall layout of a question. It is included by the
- * print_question() method.
- */
-?>
-<div id="q<?php echo $actualquestionid; ?>" class="que <?php echo $question->qtype; ?> clearfix">
-  <div class="info">
-    <h2 class="no"><span class="accesshide">Question </span><?php echo $number;
-    if ($editlink) { ?>
-      <span class="edit"><?php echo $editlink; ?></span>
-    <?php } ?></h2><?php
-    if ($grade) { ?>
-      <div class="grade">
-        <?php echo get_string('marks', 'quiz').': '.$grade; ?>
-      </div>
-    <?php }
-    $this->print_question_flag($question, $state, $options->flags); ?>
-  </div>
-  <div class="content">
-    <?php $this->print_question_formulation_and_controls($question, $state, $cmoptions, $options);
-    if ($generalfeedback) { ?>
-      <div class="generalfeedback">
-        <?php echo $generalfeedback ?>
-      </div>
-    <?php }
-    if ($comment) { ?>
-      <div class="comment">
-        <?php
-          echo get_string('comment', 'quiz').': ';
-          echo $comment;
-        ?>
-      </div>
-    <?php }
-    echo $commentlink;  ?>
-    <div class="grading">
-      <?php $this->print_question_grading_details($question, $state, $cmoptions, $options); ?>
-    </div><?php
-    if ($history) { ?>
-      <div class="history">
-        <?php
-          print_string('history', 'quiz');
-          echo $history;
-        ?>
-      </div>
-    <?php } ?>
-  </div>
-</div>
index bc3b891..900529c 100644 (file)
@@ -158,8 +158,8 @@ abstract class question_definition {
      * inappropriate.
      * @return string|null a plain text summary of this question.
      */
-    public function get_question_summary() {
-        return html_to_text($this->format_questiontext(), 0, false);
+    public function get_question_summary(question_attempt $qa) {
+        return html_to_text($this->format_questiontext($qa), 0, false);
     }
 
     /**
@@ -231,22 +231,23 @@ abstract class question_definition {
      *      parts of the question do not need to be cleaned, and student input does.
      * @return string the text formatted for output by format_text.
      */
-    public function format_text($text, $clean = false) {
+    public function format_text($text, $qa, $component, $filearea, $clean = false) {
         $formatoptions = new stdClass;
         $formatoptions->noclean = !$clean;
         $formatoptions->para = false;
-
+// TODO $itemid needs to be an argument too.
+        $text = $qa->rewrite_pluginfile_urls($text, $component, $filearea);
         return format_text($text, $this->questiontextformat, $formatoptions);
     }
 
     /** @return the result of applying {@link format_text()} to the question text. */
-    public function format_questiontext() {
-        return $this->format_text($this->questiontext);
+    public function format_questiontext($qa) {
+        return $this->format_text($this->questiontext, $qa, 'question', 'questiontext');
     }
 
     /** @return the result of applying {@link format_text()} to the general feedback. */
-    public function format_generalfeedback() {
-        return $this->format_text($this->generalfeedback);
+    public function format_generalfeedback($qa) {
+        return $this->format_text($this->generalfeedback, $qa, 'question', 'generalfeedback');
     }
 }
 
index 57e32a4..b2d5dd7 100644 (file)
@@ -32,7 +32,7 @@
  * @copyright 2009 The Open University
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-abstract class qtype_renderer extends renderer_base {
+abstract class qtype_renderer extends plugin_renderer_base {
     /**
      * Generate the display of the formulation part of the question. This is the
      * area that contains the quetsion text, and the controls for students to
@@ -45,7 +45,7 @@ abstract class qtype_renderer extends renderer_base {
      */
     public function formulation_and_controls(question_attempt $qa,
             question_display_options $options) {
-        return $qa->get_question()->questiontext;
+        return $qa->get_question()->format_questiontext($qa);
     }
 
     /**
@@ -163,7 +163,7 @@ abstract class qtype_renderer extends renderer_base {
      * @return string HTML fragment.
      */
     protected function general_feedback(question_attempt $qa) {
-        return $qa->get_question()->format_generalfeedback();
+        return $qa->get_question()->format_generalfeedback($qa);
     }
 
     /**
@@ -200,8 +200,6 @@ abstract class qtype_renderer extends renderer_base {
      * @return string html fragment.
      */
     function feedback_image($fraction, $selected = true) {
-        global $OUTPUT;
-
         $state = question_state::graded_state_for_fraction($fraction);
 
         if ($state == question_state::$gradedright) {
@@ -218,7 +216,7 @@ abstract class qtype_renderer extends renderer_base {
         }
 
         $attributes = array(
-            'src' => $OUTPUT->pix_url('i/' . $icon),
+            'src' => $this->output->pix_url('i/' . $icon),
             'alt' => get_string($state->get_feedback_class(), 'question'),
             'class' => 'questioncorrectnessicon',
         );
diff --git a/question/type/simpletest/testquestionbase.php b/question/type/simpletest/testquestionbase.php
new file mode 100644 (file)
index 0000000..156d81d
--- /dev/null
@@ -0,0 +1,117 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+
+/**
+ * Unit tests for the question definition base classes.
+ *
+ * @package moodlecore
+ * @subpackage questiontypes
+ * @copyright 2008 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once($CFG->dirroot . '/question/type/questiontype.php');
+
+
+class test_response_answer_comparer implements question_response_answer_comparer {
+    protected $answers = array();
+
+    public function __construct($answers) {
+        $this->answers = $answers;
+    }
+
+    public function get_answers() {
+        return $this->answers;
+    }
+
+    public function compare_response_with_answer(array $response, question_answer $answer) {
+        return $response['answer'] == $answer->answer;
+    }
+}
+
+/**
+ * Tests for {@link question_first_matching_answer_grading_strategy}.
+ *
+ * @copyright 2008 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class question_first_matching_answer_grading_strategy_test extends UnitTestCase {
+    public function setUp() {
+    }
+
+    public function tearDown() {
+    }
+
+    public function test_no_answers_gives_null() {
+        $question = new test_response_answer_comparer(array());
+        $strategy = new question_first_matching_answer_grading_strategy($question);
+        $this->assertNull($strategy->grade(array()));
+    }
+
+    public function test_matching_answer_returned1() {
+        $answer = new question_answer('frog', 1, '');
+        $question = new test_response_answer_comparer(array($answer));
+        $strategy = new question_first_matching_answer_grading_strategy($question);
+        $this->assertIdentical($answer, $strategy->grade(array('answer' => 'frog')));
+    }
+
+    public function test_matching_answer_returned2() {
+        $answer = new question_answer('frog', 1, '');
+        $answer2 = new question_answer('frog', 0.5, '');
+        $question = new test_response_answer_comparer(array($answer, $answer2));
+        $strategy = new question_first_matching_answer_grading_strategy($question);
+        $this->assertIdentical($answer, $strategy->grade(array('answer' => 'frog')));
+    }
+
+    public function test_no_matching_answer_gives_null() {
+        $answer = new question_answer('frog', 1, '');
+        $answer2 = new question_answer('frog', 0.5, '');
+        $question = new test_response_answer_comparer(array($answer, $answer2));
+        $strategy = new question_first_matching_answer_grading_strategy($question);
+        $this->assertNull($strategy->grade(array('answer' => 'toad')));
+    }
+}
+
+
+/**
+ * Test for question_hint and subclasses.
+ *
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class question_hint_test extends UnitTestCase {
+    public function test_basic() {
+        $row = new stdClass;
+        $row->hint = 'A hint';
+        $hint = question_hint::load_from_record($row);
+        $this->assertEqual($row->hint, $hint->hint);
+    }
+
+    public function test_with_parts() {
+        $row = new stdClass;
+        $row->hint = 'A hint';
+        $row->shownumcorrect = 1;
+        $row->clearwrong = 1;
+
+        $hint = question_hint_with_parts::load_from_record($row);
+        $this->assertEqual($row->hint, $hint->hint);
+        $this->assertTrue($hint->shownumcorrect);
+        $this->assertTrue($hint->clearwrong);
+    }
+    
+}
diff --git a/question/type/truefalse/display.html b/question/type/truefalse/display.html
deleted file mode 100644 (file)
index 344f3d8..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<div class="qtext">
-  <?php echo $questiontext; ?>
-</div>
-
-<div class="ablock clearfix">
-  <div class="prompt">
-    <?php print_string('answer', 'quiz') ?>:
-  </div>
-
-  <div class="answer">
-    <span <?php echo 'class="r0 '.$trueclass.'"'; ?>>
-        <?php echo $radiotrue ?>
-        <?php echo $truefeedbackimg; ?>
-    </span>
-    <span <?php echo 'class="r1 '.$falseclass.'"'; ?>>
-        <?php echo $radiofalse ?>
-        <?php echo $falsefeedbackimg; ?>
-    </span>
-  </div>
-    <?php if ($feedback) { ?>
-        <div class="feedback">
-            <?php echo $feedback ?>
-        </div>
-    <?php } ?>
-    <?php $this->print_question_submit_buttons($question, $state, $cmoptions, $options); ?>
-</div>
index 4714408..1cb13cb 100644 (file)
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
+/**
+ * Defines the editing form for the true-false question type.
+ *
+ * @package qtype_truefalse
+ * @copyright &copy; 2007 Jamie Pratt
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
 if (!defined('MOODLE_INTERNAL')) {
     die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
 }
 
 require_once($CFG->dirroot.'/question/type/edit_question_form.php');
 
+
 /**
- * Defines the editing form for the thruefalse question type.
+ * True-false question editing form definition.
  *
  * @copyright &copy; 2006 The Open University
- * @author T.J.Hunt@open.ac.uk
- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package questionbank
- * @subpackage questiontypes
- */
-
-/**
- * truefalse editing form definition.
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class question_edit_truefalse_form extends question_edit_form {
     /**
@@ -40,7 +43,7 @@ class question_edit_truefalse_form extends question_edit_form {
      *
      * @param object $mform the form being built.
      */
-    function definition_inner(&$mform) {
+    protected function definition_inner($mform) {
         $mform->addElement('select', 'correctanswer', get_string('correctanswer', 'qtype_truefalse'),
                 array(0 => get_string('false', 'qtype_truefalse'), 1 => get_string('true', 'qtype_truefalse')));
 
@@ -50,19 +53,23 @@ class question_edit_truefalse_form extends question_edit_form {
         $mform->addElement('editor', 'feedbackfalse', get_string('feedbackfalse', 'qtype_truefalse'), null, $this->editoroptions);
         $mform->setType('feedbackfalse', PARAM_RAW);
 
-        // Fix penalty factor at 1.
-        $mform->setDefault('penalty', 1);
-        $mform->freeze('penalty');
+        $mform->addElement('header', 'multitriesheader', get_string('settingsformultipletries', 'question'));
+
+        $mform->addElement('hidden', 'penalty', 1);
+
+        $mform->addElement('static', 'penaltymessage', get_string('penaltyforeachincorrecttry', 'question'), 1);
+        $mform->addHelpButton('penaltymessage', 'penaltyforeachincorrecttry', 'question');
     }
 
-    function set_data($question) {
+    public function set_data($question) {
         if (!empty($question->options->trueanswer)) {
             $trueanswer = $question->options->answers[$question->options->trueanswer];
+            $question->correctanswer = ($trueanswer->fraction != 0);
+
             $draftid = file_get_submitted_draft_itemid('trueanswer');
             $answerid = $question->options->trueanswer;
             $text = $trueanswer->feedback;
 
-            $question->correctanswer = ($trueanswer->fraction != 0);
             $question->feedbacktrue = array();
             $question->feedbacktrue['text'] = $trueanswer->feedback;
             $question->feedbacktrue['format'] = $trueanswer->feedbackformat;
@@ -77,8 +84,10 @@ class question_edit_truefalse_form extends question_edit_form {
             );
             $question->feedbacktrue['itemid'] = $draftid;
         }
+
         if (!empty($question->options->falseanswer)) {
             $falseanswer = $question->options->answers[$question->options->falseanswer];
+
             $draftid = file_get_submitted_draft_itemid('falseanswer');
             $answerid = $question->options->falseanswer;
             $text = $falseanswer->feedback;
@@ -100,7 +109,7 @@ class question_edit_truefalse_form extends question_edit_form {
         parent::set_data($question);
     }
 
-    function qtype() {
+    public function qtype() {
         return 'truefalse';
     }
 }
index 488de8c..5465b36 100644 (file)
 
 $string['addingtruefalse'] = 'Adding a True/False question';
 $string['correctanswer'] = 'Correct answer';
+$string['correctanswerfalse'] = 'The correct answer is \'False\'.';
+$string['correctanswertrue'] = 'The correct answer is \'True\'.';
 $string['editingtruefalse'] = 'Editing a True/False question';
 $string['false'] = 'False';
 $string['feedbackfalse'] = 'Feedback for the response \'False\'.';
 $string['feedbacktrue'] = 'Feedback for the response \'True\'.';
+$string['pleaseselectananswer'] = 'Please select an answer.';
+$string['selectone'] = 'Select one:';
 $string['true'] = 'True';
 $string['truefalse'] = 'True/False';
 $string['truefalse_help'] = 'In response to a question (that may include a image) the respondent chooses from true or false.';
diff --git a/question/type/truefalse/old_questiontype.php b/question/type/truefalse/old_questiontype.php
deleted file mode 100644 (file)
index 1dc2cc1..0000000
+++ /dev/null
@@ -1,317 +0,0 @@
-<?php
-
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-defined('MOODLE_INTERNAL') || die();
-
-/////////////////
-/// TRUEFALSE ///
-/////////////////
-
-/// QUESTION TYPE CLASS //////////////////
-/**
- * @package questionbank
- * @subpackage questiontypes
- */
-class question_truefalse_qtype extends default_questiontype {
-
-    function name() {
-        return 'truefalse';
-    }
-
-    function save_question_options($question) {
-        global $DB;
-        $result = new stdClass;
-        $context = $question->context;
-
-        // Fetch old answer ids so that we can reuse them
-        $oldanswers = $DB->get_records('question_answers',
-                array('question' => $question->id), 'id ASC');
-
-        // Save the true answer - update an existing answer if possible.
-        $answer = array_shift($oldanswers);
-        if (!$answer) {
-            $answer = new stdClass();
-            $answer->question = $question->id;
-            $answer->answer = '';
-            $answer->feedback = '';
-            $answer->id = $DB->insert_record('question_answers', $answer);
-        }
-
-        $answer->answer   = get_string('true', 'quiz');
-        $answer->fraction = $question->correctanswer;
-        $answer->feedback = $this->import_or_save_files($question->feedbacktrue,
-                $context, 'question', 'answerfeedback', $answer->id);
-        $answer->feedbackformat = $question->feedbacktrue['format'];
-        $DB->update_record('question_answers', $answer);
-        $trueid = $answer->id;
-
-        // Save the false answer - update an existing answer if possible.
-        $answer = array_shift($oldanswers);
-        if (!$answer) {
-            $answer = new stdClass();
-            $answer->question = $question->id;
-            $answer->answer = '';
-            $answer->feedback = '';
-            $answer->id = $DB->insert_record('question_answers', $answer);
-        }
-
-        $answer->answer   = get_string('false', 'quiz');
-        $answer->fraction = 1 - (int)$question->correctanswer;
-        $answer->feedback = $this->import_or_save_files($question->feedbackfalse,
-                $context, 'question', 'answerfeedback', $answer->id);
-        $answer->feedbackformat = $question->feedbackfalse['format'];
-        $DB->update_record('question_answers', $answer);
-        $falseid = $answer->id;
-
-        // Delete any left over old answer records.
-        $fs = get_file_storage();
-        foreach($oldanswers as $oldanswer) {
-            $fs->delete_area_files($context->id, 'question', 'answerfeedback', $oldanswer->id);
-            $DB->delete_records('question_answers', array('id' => $oldanswer->id));
-        }
-
-        // Save question options in question_truefalse table
-        if ($options = $DB->get_record('question_truefalse', array('question' => $question->id))) {
-            // No need to do anything, since the answer IDs won't have changed
-            // But we'll do it anyway, just for robustness
-            $options->trueanswer  = $trueid;
-            $options->falseanswer = $falseid;
-            $DB->update_record('question_truefalse', $options);
-        } else {
-            $options = new stdClass();
-            $options->question    = $question->id;
-            $options->trueanswer  = $trueid;
-            $options->falseanswer = $falseid;
-            $DB->insert_record('question_truefalse', $options);
-        }
-
-        return true;
-    }
-
-    /**
-    * Loads the question type specific options for the question.
-    */
-    function get_question_options(&$question) {
-        global $DB, $OUTPUT;
-        // Get additional information from database
-        // and attach it to the question object
-        if (!$question->options = $DB->get_record('question_truefalse', array('question' => $question->id))) {
-            echo $OUTPUT->notification('Error: Missing question options!');
-            return false;
-        }
-        // Load the answers
-        if (!$question->options->answers = $DB->get_records('question_answers', array('question' =>  $question->id), 'id ASC')) {
-           echo $OUTPUT->notification('Error: Missing question answers for truefalse question ' . $question->id . '!');
-           return false;
-        }
-
-        return true;
-    }
-
-    function delete_question($questionid, $contextid) {
-        global $DB;
-        $DB->delete_records('question_truefalse', array('question' => $questionid));
-
-        parent::delete_question($questionid, $contextid);
-    }
-
-    function compare_responses($question, $state, $teststate) {
-        if (isset($state->responses['']) && isset($teststate->responses[''])) {
-            return $state->responses[''] === $teststate->responses[''];
-        } else if (isset($teststate->responses['']) && $teststate->responses[''] === '' &&
-                !isset($state->responses[''])) {
-            // Nothing selected in the past, and nothing selected now.
-            return true;
-        }
-        return false;
-    }
-
-    function get_correct_responses(&$question, &$state) {
-        // The correct answer is the one which gives full marks
-        foreach ($question->options->answers as $answer) {
-            if (((int) $answer->fraction) === 1) {
-                return array('' => $answer->id);
-            }
-        }
-        return null;
-    }
-
-    /**
-    * Prints the main content of the question including any interactions
-    */
-    function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
-        global $CFG;
-        $context = $this->get_context_by_category_id($question->category);
-
-        $readonly = $options->readonly ? ' disabled="disabled"' : '';
-
-        $formatoptions = new stdClass;
-        $formatoptions->noclean = true;
-        $formatoptions->para = false;
-
-        // Print question formulation
-        $questiontext = format_text($question->questiontext,
-                         $question->questiontextformat,
-                         $formatoptions, $cmoptions->course);
-
-        $answers = &$question->options->answers;
-        $trueanswer = &$answers[$question->options->trueanswer];
-        $falseanswer = &$answers[$question->options->falseanswer];
-        $correctanswer = ($trueanswer->fraction == 1) ? $trueanswer : $falseanswer;
-
-        $trueclass = '';
-        $falseclass = '';
-        $truefeedbackimg = '';
-        $falsefeedbackimg = '';
-
-        // Work out which radio button to select (if any)
-        if (isset($state->responses[''])) {
-            $response = $state->responses[''];
-        } else {
-            $response = '';
-        }
-        $truechecked = ($response == $trueanswer->id) ? ' checked="checked"' : '';
-        $falsechecked = ($response == $falseanswer->id) ? ' checked="checked"' : '';
-
-        // Work out visual feedback for answer correctness.
-        if ($options->feedback) {
-            if ($truechecked) {
-                $trueclass = question_get_feedback_class($trueanswer->fraction);
-            } else if ($falsechecked) {
-                $falseclass = question_get_feedback_class($falseanswer->fraction);
-            }
-        }
-        if ($options->feedback || $options->correct_responses) {
-            if (isset($answers[$response])) {
-                $truefeedbackimg = question_get_feedback_image($trueanswer->fraction, !empty($truechecked) && $options->feedback);
-                $falsefeedbackimg = question_get_feedback_image($falseanswer->fraction, !empty($falsechecked) && $options->feedback);
-            }
-        }
-
-        $inputname = ' name="'.$question->name_prefix.'" ';
-        $trueid    = $question->name_prefix.'true';
-        $falseid   = $question->name_prefix.'false';
-
-        $radiotrue = '<input type="radio"' . $truechecked . $readonly . $inputname
-            . 'id="'.$trueid . '" value="' . $trueanswer->id . '" /><label for="'.$trueid . '">'
-            . s($trueanswer->answer) . '</label>';
-        $radiofalse = '<input type="radio"' . $falsechecked . $readonly . $inputname
-            . 'id="'.$falseid . '" value="' . $falseanswer->id . '" /><label for="'.$falseid . '">'
-            . s($falseanswer->answer) . '</label>';
-
-        $feedback = '';
-        if ($options->feedback and isset($answers[$response])) {
-            $chosenanswer = $answers[$response];
-            $chosenanswer->feedback = quiz_rewrite_question_urls($chosenanswer->feedback, 'pluginfile.php', $context->id, 'question', 'answerfeedback', array($state->attempt, $state->question), $chosenanswer->id);
-            $feedback = format_text($chosenanswer->feedback, $chosenanswer->feedbackformat, $formatoptions, $cmoptions->course);
-        }
-
-        include("$CFG->dirroot/question/type/truefalse/display.html");
-    }
-
-    function move_files($questionid, $oldcontextid, $newcontextid) {
-        parent::move_files($questionid, $oldcontextid, $newcontextid);
-        $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid);
-    }
-
-    protected function delete_files($questionid, $contextid) {
-        parent::delete_files($questionid, $contextid);
-        $this->delete_files_in_answers($questionid, $contextid);
-    }
-
-    function check_file_access($question, $state, $options, $contextid, $component,
-            $filearea, $args) {
-        if ($component == 'question' && $filearea == 'answerfeedback') {
-
-            $answerid = reset($args); // itemid is answer id.
-            $answers = &$question->options->answers;
-            if (isset($state->responses[''])) {
-                $response = $state->responses[''];
-            } else {
-                $response = '';
-            }
-
-            return $options->feedback && isset($answers[$response]) && $answerid == $response;
-
-        } else {
-            return parent::check_file_access($question, $state, $options, $contextid, $component,
-                    $filearea, $args);
-        }
-    }
-
-    function grade_responses(&$question, &$state, $cmoptions) {
-        if (isset($state->responses['']) && isset($question->options->answers[$state->responses['']])) {
-            $state->raw_grade = $question->options->answers[$state->responses['']]->fraction * $question->maxgrade;
-        } else {
-            $state->raw_grade = 0;
-        }
-        // Only allow one attempt at the question
-        $state->penalty = 1 * $question->maxgrade;
-
-        // mark the state as graded
-        $state->event = ($state->event ==  QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE;
-
-        return true;
-    }
-
-    function get_actual_response($question, $state) {
-        if (isset($question->options->answers[$state->responses['']])) {
-            $responses[] = $question->options->answers[$state->responses['']]->answer;
-        } else {
-            $responses[] = '';
-        }
-        return $responses;
-    }
-    /**
-     * @param object $question
-     * @return mixed either a integer score out of 1 that the average random
-     * guess by a student might give or an empty string which means will not
-     * calculate.
-     */
-    function get_random_guess_score($question) {
-        return 0.5;
-    }
-
-    /**
-     * Runs all the code required to set up and save an essay question for testing purposes.
-     * Alternate DB table prefix may be used to facilitate data deletion.
-     */
-    function generate_test($name, $courseid = null) {
-        global $DB;
-        list($form, $question) = parent::generate_test($name, $courseid);
-        $question->category = $form->category;
-
-        $form->questiontext = "This question is really stupid";
-        $form->penalty = 1;
-        $form->defaultgrade = 1;
-        $form->correctanswer = 0;
-        $form->feedbacktrue = 'Can you justify such a hasty judgment?';
-        $form->feedbackfalse = 'Wisdom has spoken!';
-
-        if ($courseid) {
-            $course = $DB->get_record('course', array('id' => $courseid));
-        }
-
-        return $this->save_question($question, $form);
-    }
-}
-//// END OF CLASS ////
-
-//////////////////////////////////////////////////////////////////////////
-//// INITIATION - Without this line the question type is not in use... ///
-//////////////////////////////////////////////////////////////////////////
-question_register_questiontype(new question_truefalse_qtype());
index 4b92257..b10a099 100644 (file)
  */
 class qtype_truefalse extends question_type {
     public function save_question_options($question) {
-        $result = new stdClass;
-
-        // fetch old answer ids so that we can reuse them
-        if (!$oldanswers = get_records("question_answers", "question", $question->id, "id ASC")) {
-            $oldanswers = array();
+        global $DB;
+        $result = new stdClass();
+        $context = $question->context;
+
+        // Fetch old answer ids so that we can reuse them
+        $oldanswers = $DB->get_records('question_answers',
+                array('question' => $question->id), 'id ASC');
+
+        // Save the true answer - update an existing answer if possible.
+        $answer = array_shift($oldanswers);
+        if (!$answer) {
+            $answer = new stdClass();
+            $answer->question = $question->id;
+            $answer->answer = '';
+            $answer->feedback = '';
+            $answer->id = $DB->insert_record('question_answers', $answer);
         }
 
-        // Save answer 'True'
-        if ($true = array_shift($oldanswers)) {  // Existing answer, so reuse it
-            $true->answer   = get_string("true", "quiz");
-            $true->fraction = $question->correctanswer;
-            $true->feedback = $question->feedbacktrue;
-            if (!update_record("question_answers", $true)) {
-                $result->error = "Could not update quiz answer \"true\")!";
-                return $result;
-            }
-        } else {
-            unset($true);
-            $true->answer   = get_string("true", "quiz");
-            $true->question = $question->id;
-            $true->fraction = $question->correctanswer;
-            $true->feedback = $question->feedbacktrue;
-            if (!$true->id = insert_record("question_answers", $true)) {
-                $result->error = "Could not insert quiz answer \"true\")!";
-                return $result;
-            }
-        }
-
-        // Save answer 'False'
-        if ($false = array_shift($oldanswers)) {  // Existing answer, so reuse it
-            $false->answer   = get_string("false", "quiz");
-            $false->fraction = 1 - (int)$question->correctanswer;
-            $false->feedback = $question->feedbackfalse;
-            if (!update_record("question_answers", $false)) {
-                $result->error = "Could not insert quiz answer \"false\")!";
-                return $result;
-            }
-        } else {
-            unset($false);
-            $false->answer   = get_string("false", "quiz");
-            $false->question = $question->id;
-            $false->fraction = 1 - (int)$question->correctanswer;
-            $false->feedback = $question->feedbackfalse;
-            if (!$false->id = insert_record("question_answers", $false)) {
-                $result->error = "Could not insert quiz answer \"false\")!";
-                return $result;
-            }
+        $answer->answer   = get_string('true', 'quiz');
+        $answer->fraction = $question->correctanswer;
+        $answer->feedback = $this->import_or_save_files($question->feedbacktrue,
+                $context, 'question', 'answerfeedback', $answer->id);
+        $answer->feedbackformat = $question->feedbacktrue['format'];
+        $DB->update_record('question_answers', $answer);
+        $trueid = $answer->id;
+
+        // Save the false answer - update an existing answer if possible.
+        $answer = array_shift($oldanswers);
+        if (!$answer) {
+            $answer = new stdClass();
+            $answer->question = $question->id;
+            $answer->answer = '';
+            $answer->feedback = '';
+            $answer->id = $DB->insert_record('question_answers', $answer);
         }
 
-        // delete any leftover old answer records (there couldn't really be any, but who knows)
-        if (!empty($oldanswers)) {
-            foreach($oldanswers as $oa) {
-                delete_records('question_answers', 'id', $oa->id);
-            }
+        $answer->answer   = get_string('false', 'quiz');
+        $answer->fraction = 1 - (int)$question->correctanswer;
+        $answer->feedback = $this->import_or_save_files($question->feedbackfalse,
+                $context, 'question', 'answerfeedback', $answer->id);
+        $answer->feedbackformat = $question->feedbackfalse['format'];
+        $DB->update_record('question_answers', $answer);
+        $falseid = $answer->id;
+
+        // Delete any left over old answer records.
+        $fs = get_file_storage();
+        foreach($oldanswers as $oldanswer) {
+            $fs->delete_area_files($context->id, 'question', 'answerfeedback', $oldanswer->id);
+            $DB->delete_records('question_answers', array('id' => $oldanswer->id));
         }
 
         // Save question options in question_truefalse table
-        if ($options = get_record("question_truefalse", "question", $question->id)) {
+        if ($options = $DB->get_record('question_truefalse', array('question' => $question->id))) {
             // No need to do anything, since the answer IDs won't have changed
             // But we'll do it anyway, just for robustness
-            $options->trueanswer  = $true->id;
-            $options->falseanswer = $false->id;
-            if (!update_record("question_truefalse", $options)) {
-                $result->error = "Could not update quiz truefalse options! (id=$options->id)";
-                return $result;
-            }
+            $options->trueanswer  = $trueid;
+            $options->falseanswer = $falseid;
+            $DB->update_record('question_truefalse', $options);
         } else {
-            unset($options);
+            $options = new stdClass();
             $options->question    = $question->id;
-            $options->trueanswer  = $true->id;
-            $options->falseanswer = $false->id;
-            if (!insert_record("question_truefalse", $options)) {
-                $result->error = "Could not insert quiz truefalse options!";
-                return $result;
-            }
+            $options->trueanswer  = $trueid;
+            $options->falseanswer = $falseid;
+            $DB->insert_record('question_truefalse', $options);
         }
 
         $this->save_hints($question);
@@ -116,17 +105,21 @@ class qtype_truefalse extends question_type {
     }
 
     /**
-    * Loads the question type specific options for the question.
-    */
+     * Loads the question type specific options for the question.
+     */
     public function get_question_options($question) {
+        global $DB, $OUTPUT;
         // Get additional information from database
         // and attach it to the question object
-        if (!$question->options = get_record('question_truefalse', 'question', $question->id)) {
-            notify('Error: Missing question options!');
+        if (!$question->options = $DB->get_record('question_truefalse', array('question' => $question->id))) {
+            echo $OUTPUT->notification('Error: Missing question options!');
             return false;
         }
-
-        parent::get_question_options($question);
+        // Load the answers
+        if (!$question->options->answers = $DB->get_records('question_answers', array('question' =>  $question->id), 'id ASC')) {
+           echo $OUTPUT->notification('Error: Missing question answers for truefalse question ' . $question->id . '!');
+           return false;
+        }
 
         return true;
     }
@@ -143,15 +136,41 @@ class qtype_truefalse extends question_type {
         $question->falsefeedback = $answers[$questiondata->options->falseanswer]->feedback;
     }
 
-    /**
-    * Deletes question from the question-type specific tables
-    *
-    * @return boolean Success/Failure
-    * @param object $question  The question being deleted
-    */
-    public function delete_question($questionid) {
-        delete_records("question_truefalse", "question", $questionid);
-        return parent::delete_question($questionid);
+    function delete_question($questionid, $contextid) {
+        global $DB;
+        $DB->delete_records('question_truefalse', array('question' => $questionid));
+
+        parent::delete_question($questionid, $contextid);
+    }
+
+    function move_files($questionid, $oldcontextid, $newcontextid) {
+        parent::move_files($questionid, $oldcontextid, $newcontextid);
+        $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid);
+    }
+
+    protected function delete_files($questionid, $contextid) {
+        parent::delete_files($questionid, $contextid);
+        $this->delete_files_in_answers($questionid, $contextid);
+    }
+
+    function check_file_access($question, $state, $options, $contextid, $component,
+            $filearea, $args) {
+        if ($component == 'question' && $filearea == 'answerfeedback') {
+
+            $answerid = reset($args); // itemid is answer id.
+            $answers = &$question->options->answers;
+            if (isset($state->responses[''])) {
+                $response = $state->responses[''];
+            } else {
+                $response = '';
+            }
+
+            return $options->feedback && isset($answers[$response]) && $answerid == $response;
+
+        } else {
+            return parent::check_file_access($question, $state, $options, $contextid, $component,
+                    $filearea, $args);
+        }
     }
 
     function get_random_guess_score($questiondata) {
@@ -171,128 +190,4 @@ class qtype_truefalse extends question_type {
             )
         );
     }
-
-/// BACKUP FUNCTIONS ////////////////////////////
-
-    /*
-     * Backup the data in a truefalse question
-     *
-     * This is used in question/backuplib.php
-     */
-    public function backup($bf,$preferences,$question,$level=6) {
-
-        $status = true;
-
-        $truefalses = get_records("question_truefalse","question",$question,"id");
-        //If there are truefalses
-        if ($truefalses) {
-            //Iterate over each truefalse
-            foreach ($truefalses as $truefalse) {
-                $status = fwrite ($bf,start_tag("TRUEFALSE",$level,true));
-                //Print truefalse contents
-                fwrite ($bf,full_tag("TRUEANSWER",$level+1,false,$truefalse->trueanswer));
-                fwrite ($bf,full_tag("FALSEANSWER",$level+1,false,$truefalse->falseanswer));
-                $status = fwrite ($bf,end_tag("TRUEFALSE",$level,true));
-            }
-            //Now print question_answers
-            $status = question_backup_answers($bf,$preferences,$question);
-        }
-        return $status;
-    }
-
-/// RESTORE FUNCTIONS /////////////////
-
-    /*
-     * Restores the data in the question
-     *
-     * This is used in question/restorelib.php
-     */
-    public function restore($old_question_id,$new_question_id,$info,$restore) {
-
-        $status = true;
-
-        //Get the truefalse array
-        if (array_key_exists('TRUEFALSE', $info['#'])) {
-            $truefalses = $info['#']['TRUEFALSE'];
-        } else {
-            $truefalses = array();
-        }
-
-        //Iterate over truefalse
-        for($i = 0; $i < sizeof($truefalses); $i++) {
-            $tru_info = $truefalses[$i];
-
-            //Now, build the question_truefalse record structure
-            $truefalse = new stdClass;
-            $truefalse->question = $new_question_id;
-            $truefalse->trueanswer = backup_todb($tru_info['#']['TRUEANSWER']['0']['#']);
-            $truefalse->falseanswer = backup_todb($tru_info['#']['FALSEANSWER']['0']['#']);
-
-            ////We have to recode the trueanswer field
-            $answer = backup_getid($restore->backup_unique_code,"question_answers",$truefalse->trueanswer);
-            if ($answer) {
-                $truefalse->trueanswer = $answer->new_id;
-            }
-
-            ////We have to recode the falseanswer field
-            $answer = backup_getid($restore->backup_unique_code,"question_answers",$truefalse->falseanswer);
-            if ($answer) {
-                $truefalse->falseanswer = $answer->new_id;
-            }
-
-            //The structure is equal to the db, so insert the question_truefalse
-            $newid = insert_record ("question_truefalse", $truefalse);
-
-            //Do some output
-            if (($i+1) % 50 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 1000 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            if (!$newid) {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    public function restore_recode_answer($state, $restore) {
-        //answer may be empty
-        if ($state->answer) {
-            $answer = backup_getid($restore->backup_unique_code,"question_answers",$state->answer);
-            if ($answer) {
-                return $answer->new_id;
-            } else {
-                echo 'Could not recode truefalse answer id '.$state->answer.' for state '.$state->oldid.'<br />';
-            }
-        }
-    }
-
-    /**
-     * Runs all the code required to set up and save an essay question for testing purposes.
-     * Alternate DB table prefix may be used to facilitate data deletion.
-     */
-    public function generate_test($name, $courseid = null) {
-        list($form, $question) = parent::generate_test($name, $courseid);
-        $question->category = $form->category;
-
-        $form->questiontext = "This question is really stupid";
-        $form->penalty = 1;
-        $form->defaultmark = 1;
-        $form->correctanswer = 0;
-        $form->feedbacktrue = array('Can you justify such a hasty judgment?');
-        $form->feedbackfalse = array('Wisdom has spoken!');
-
-        if ($courseid) {
-            $course = get_record('course', 'id', $courseid);
-        }
-
-        return $this->save_question($question, $form, $course);
-    }
 }
index bfea585..023ed1e 100644 (file)
@@ -124,9 +124,9 @@ class qtype_truefalse_renderer extends qtype_renderer {
         $response = $qa->get_last_qt_var('answer', '');
 
         if ($response) {
-            return $question->format_text($question->truefeedback);
+            return $question->format_text($question->truefeedback, $qa, 'question', 'answerfeedback');
         } else {
-            return $question->format_text($question->falsefeedback);
+            return $question->format_text($question->falsefeedback, $qa, 'question', 'answerfeedback');
         }
     }
 
diff --git a/question/type/truefalse/simpletest/testquestion.php b/question/type/truefalse/simpletest/testquestion.php
new file mode 100644 (file)
index 0000000..17f877e
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+
+/**
+ * Unit tests for the true-false question definition class.
+ *
+ * @package qtype_truefalse
+ * @copyright 2008 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once($CFG->dirroot . '/question/engine/simpletest/helpers.php');
+
+/**
+ * Unit tests for the true-false question definition class.
+ *
+ * @copyright 2008 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_truefalse_question_test extends UnitTestCase {
+
+    /**
+     * TODO Temporary helper method to just make a questoin_attempt.
+     */
+    protected function get_a_qa() {
+        $question = test_question_maker::make_a_description_question();
+        $question->defaultmark = 3;
+        $usageid = 13;
+        return new question_attempt($question, $usageid);
+    }
+
+    public function test_is_complete_response() {
+        $question = test_question_maker::make_a_truefalse_question();
+
+        $this->assertFalse($question->is_complete_response(array()));
+        $this->assertTrue($question->is_complete_response(array('answer' => 0)));
+        $this->assertTrue($question->is_complete_response(array('answer' => 1)));
+    }
+
+    public function test_is_gradable_response() {
+        $question = test_question_maker::make_a_truefalse_question();
+
+        $this->assertFalse($question->is_gradable_response(array()));
+        $this->assertTrue($question->is_gradable_response(array('answer' => 0)));
+        $this->assertTrue($question->is_gradable_response(array('answer' => 1)));
+            }
+
+    public function test_grading() {
+        $question = test_question_maker::make_a_truefalse_question();
+
+        $this->assertEqual(array(0, question_state::$gradedwrong),
+                $question->grade_response(array('answer' => 0)));
+        $this->assertEqual(array(1, question_state::$gradedright),
+                $question->grade_response(array('answer' => 1)));
+    }
+
+    public function test_get_correct_response() {
+        $question = test_question_maker::make_a_truefalse_question();
+
+        // true
+        $this->assertIdentical(array('answer' => 1),
+                $question->get_correct_response());
+
+        // false
+        $question->rightanswer = false;
+        $this->assertIdentical(array('answer' => 0),
+                $question->get_correct_response());
+    }
+
+    public function test_get_question_summary() {
+        $tf = test_question_maker::make_a_truefalse_question();
+        $qsummary = $tf->get_question_summary($this->get_a_qa());
+        $this->assertEqual('The answer is true.', $qsummary);
+    }
+
+    public function test_summarise_response() {
+        $tf = test_question_maker::make_a_truefalse_question();
+
+        $this->assertEqual(get_string('false', 'qtype_truefalse'),
+                $tf->summarise_response(array('answer' => '0')));
+
+        $this->assertEqual(get_string('true', 'qtype_truefalse'),
+                $tf->summarise_response(array('answer' => '1')));
+    }
+
+    public function test_classify_response() {
+        $tf = test_question_maker::make_a_truefalse_question();
+        $tf->init_first_step(new question_attempt_step());
+
+        $this->assertEqual(array(
+                $tf->id => new question_classified_response(0, get_string('false', 'qtype_truefalse'), 0.0)),
+                $tf->classify_response(array('answer' => '0')));
+        $this->assertEqual(array(
+                $tf->id => new question_classified_response(1, get_string('true', 'qtype_truefalse'), 1.0)),
+                $tf->classify_response(array('answer' => '1')));
+        $this->assertEqual(array(
+                $tf->id => question_classified_response::no_response()),
+                $tf->classify_response(array()));
+    }
+}
diff --git a/question/type/truefalse/simpletest/testquestiontype.php b/question/type/truefalse/simpletest/testquestiontype.php
new file mode 100644 (file)
index 0000000..488fb50
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+
+/**
+ * Unit tests for the true-false question definition class.
+ *
+ * @package qtype_truefalse
+ * @copyright &copy; 2007 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once($CFG->dirroot . '/question/type/shortanswer/questiontype.php');
+
+/**
+ * Unit tests for the true-false question definition class.
+ *
+ * @copyright &copy; 2007 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_truefalse_test extends UnitTestCase {
+    protected $qtype;
+
+    public function setUp() {
+        $this->qtype = new qtype_truefalse();
+    }
+
+    public function tearDown() {
+        $this->qtype = null;
+    }
+
+    public function test_name() {
+        $this->assertEqual($this->qtype->name(), 'truefalse');
+    }
+
+    public function test_can_analyse_responses() {
+        $this->assertTrue($this->qtype->can_analyse_responses());
+    }
+
+    public function test_get_random_guess_score() {
+        $this->assertEqual(0.5, $this->qtype->get_random_guess_score(null));
+    }
+
+    public function test_get_possible_responses() {
+        $q = new stdClass;
+        $q->id = 1;
+        $q->options->trueanswer = 1;
+        $q->options->falseanswer = 2;
+        $q->options->answers[1] = (object) array('fraction' => 1);
+        $q->options->answers[2] = (object) array('fraction' => 0);
+
+        $this->assertEqual(array(
+            $q->id => array(
+                0 => new question_possible_response(get_string('false', 'qtype_truefalse'), 0),
+                1 => new question_possible_response(get_string('true', 'qtype_truefalse'), 1),
+                null => question_possible_response::no_response()),
+        ), $this->qtype->get_possible_responses($q));
+    }
+}