diff --git a/admin/tool/qeupgradehelper/locallib.php b/admin/tool/qeupgradehelper/locallib.php
index eda56a83ba0..f471b233be6 100644
--- a/admin/tool/qeupgradehelper/locallib.php
+++ b/admin/tool/qeupgradehelper/locallib.php
@@ -572,6 +572,7 @@ function tool_qeupgradehelper_generate_unit_test($questionsessionid, $namesuffix
'variant' => 1,
'maxmark' => {$question->maxmark},
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => '',
'rightanswer' => '',
diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php
index 922318c349b..e7f6ea423cb 100644
--- a/backup/moodle2/backup_stepslib.php
+++ b/backup/moodle2/backup_stepslib.php
@@ -209,7 +209,7 @@ abstract class backup_questions_activity_structure_step extends backup_activity_
$qas = new backup_nested_element($nameprefix . 'question_attempts');
$qa = new backup_nested_element($nameprefix . 'question_attempt', array('id'), array(
- 'slot', 'behaviour', 'questionid', 'maxmark', 'minfraction',
+ 'slot', 'behaviour', 'questionid', 'maxmark', 'minfraction', 'maxfraction',
'flagged', 'questionsummary', 'rightanswer', 'responsesummary',
'timemodified'));
diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php
index 1afa5232cd8..dea8ab05f8b 100644
--- a/backup/moodle2/restore_stepslib.php
+++ b/backup/moodle2/restore_stepslib.php
@@ -4047,6 +4047,10 @@ abstract class restore_questions_activity_structure_step extends restore_activit
$data->questionid = $question->newitemid;
$data->timemodified = $this->apply_date_offset($data->timemodified);
+ if (!property_exists($data, 'maxfraction')) {
+ $data->maxfraction = 1;
+ }
+
$newitemid = $DB->insert_record('question_attempts', $data);
$this->set_mapping($nameprefix . 'question_attempt', $oldid, $newitemid);
diff --git a/lang/en/question.php b/lang/en/question.php
index 86eab4c5bd1..60f2cd85c39 100644
--- a/lang/en/question.php
+++ b/lang/en/question.php
@@ -413,6 +413,7 @@ $string['submitted'] = 'Submit: {$a}';
$string['technicalinfo'] = 'Technical information';
$string['technicalinfo_help'] = 'This technical information is probably only useful for developers working on new question types. It may also be helpful when trying to diagnose problems with questions.';
$string['technicalinfominfraction'] = 'Minimum fraction: {$a}';
+$string['technicalinfomaxfraction'] = 'Maximum fraction: {$a}';
$string['technicalinfoquestionsummary'] = 'Question summary: {$a}';
$string['technicalinforightsummary'] = 'Right answer summary: {$a}';
$string['technicalinfostate'] = 'Question state: {$a}';
diff --git a/lib/db/install.xml b/lib/db/install.xml
index 64cb1cb140b..9d4d255557e 100644
--- a/lib/db/install.xml
+++ b/lib/db/install.xml
@@ -1392,6 +1392,7 @@
+
diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php
index 3b6c4c14c99..45f98ea9553 100644
--- a/lib/db/upgrade.php
+++ b/lib/db/upgrade.php
@@ -2605,7 +2605,21 @@ function xmldb_main_upgrade($oldversion) {
// Main savepoint reached.
upgrade_main_savepoint(true, 2013100400.01);
+ }
+ if ($oldversion < 2013100800.00) {
+
+ // Define field maxfraction to be added to question_attempts.
+ $table = new xmldb_table('question_attempts');
+ $field = new xmldb_field('maxfraction', XMLDB_TYPE_NUMBER, '12, 7', null, XMLDB_NOTNULL, null, '1', 'minfraction');
+
+ // Conditionally launch add field maxfraction.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2013100800.00);
}
return true;
diff --git a/mod/quiz/attemptlib.php b/mod/quiz/attemptlib.php
index 73164340e5a..cb05a5466bf 100644
--- a/mod/quiz/attemptlib.php
+++ b/mod/quiz/attemptlib.php
@@ -745,6 +745,26 @@ class quiz_attempt {
array_intersect(array_keys($teachersgroups), array_keys($studentsgroups));
}
+ /**
+ * Get extra summary information about this attempt.
+ *
+ * Some behaviours may be able to provide interesting summary information
+ * about the attempt as a whole, and this method provides access to that data.
+ * To see how this works, try setting a quiz to one of the CBM behaviours,
+ * and then look at the extra information displayed at the top of the quiz
+ * review page once you have sumitted an attempt.
+ *
+ * In the return value, the array keys are identifiers of the form
+ * qbehaviour_behaviourname_meaningfullkey. For qbehaviour_deferredcbm_highsummary.
+ * The values are arrays with two items, title and content. Each of these
+ * will be either a string, or a renderable.
+ *
+ * @return array as described above.
+ */
+ public function get_additional_summary_data(question_display_options $options) {
+ return $this->quba->get_summary_information($options);
+ }
+
/**
* Get the overall feedback corresponding to a particular mark.
* @param $grade a particular grade.
diff --git a/mod/quiz/comment.php b/mod/quiz/comment.php
index 16d3c0ee3eb..77177e41e32 100644
--- a/mod/quiz/comment.php
+++ b/mod/quiz/comment.php
@@ -55,7 +55,7 @@ echo $OUTPUT->heading(format_string($attemptobj->get_question_name($slot)));
// Process any data that was submitted.
if (data_submitted() && confirm_sesskey()) {
- if (optional_param('submit', false, PARAM_BOOL) && question_behaviour::is_manual_grade_in_range($attemptobj->get_uniqueid(), $slot)) {
+ if (optional_param('submit', false, PARAM_BOOL) && question_engine::is_manual_grade_in_range($attemptobj->get_uniqueid(), $slot)) {
$transaction = $DB->start_delegated_transaction();
$attemptobj->process_submitted_actions(time());
$transaction->allow_commit();
diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php
index 432fb24af4d..e73c79a26d7 100644
--- a/mod/quiz/lang/en/quiz.php
+++ b/mod/quiz/lang/en/quiz.php
@@ -527,7 +527,7 @@ To arrange the questions over a number of pages, click the Repaginate button and
$string['orderingquiz'] = 'Order and paging';
$string['orderingquizx'] = 'Order and paging: {$a}';
$string['outcomesadvanced'] = 'Outcomes are advanced settings';
-$string['outof'] = '{$a->grade} out of a maximum of {$a->maxgrade}';
+$string['outof'] = '{$a->grade} out of {$a->maxgrade}';
$string['outofpercent'] = '{$a->grade} out of a maximum of {$a->maxgrade} ({$a->percent}%)';
$string['outofshort'] = '{$a->grade}/{$a->maxgrade}';
$string['overallfeedback'] = 'Overall feedback';
diff --git a/mod/quiz/report/grading/report.php b/mod/quiz/report/grading/report.php
index 9d31f04b5ca..4173576db67 100644
--- a/mod/quiz/report/grading/report.php
+++ b/mod/quiz/report/grading/report.php
@@ -461,7 +461,7 @@ class quiz_grading_report extends quiz_default_report {
foreach ($qubaids as $qubaid) {
foreach ($slots as $slot) {
- if (!question_behaviour::is_manual_grade_in_range($qubaid, $slot)) {
+ if (!question_engine::is_manual_grade_in_range($qubaid, $slot)) {
return false;
}
}
diff --git a/mod/quiz/review.php b/mod/quiz/review.php
index eacc703e27b..ad34fa55633 100644
--- a/mod/quiz/review.php
+++ b/mod/quiz/review.php
@@ -228,6 +228,9 @@ if ($options->marks >= question_display_options::MARK_AND_MAX && quiz_has_grades
}
}
+// Any additional summary data from the behaviour.
+$summarydata = array_merge($summarydata, $attemptobj->get_additional_summary_data($options));
+
// Feedback if there is any, and the user is allowed to see it now.
$feedback = $attemptobj->get_overall_feedback($grade);
if ($options->overallfeedback && $feedback) {
diff --git a/question/behaviour/adaptive/behaviourtype.php b/question/behaviour/adaptive/behaviourtype.php
new file mode 100644
index 00000000000..607ea3a3606
--- /dev/null
+++ b/question/behaviour/adaptive/behaviourtype.php
@@ -0,0 +1,39 @@
+.
+
+/**
+ * Question behaviour type for adaptive behaviour.
+ *
+ * @package qbehaviour_adaptive
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Question behaviour type information for adaptive behaviour.
+ *
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qbehaviour_adaptive_type extends question_behaviour_type {
+ public function is_archetypal() {
+ return true;
+ }
+}
diff --git a/question/behaviour/adaptivenopenalty/behaviour.php b/question/behaviour/adaptivenopenalty/behaviour.php
index 0740fa6b192..7d02d2b54f8 100644
--- a/question/behaviour/adaptivenopenalty/behaviour.php
+++ b/question/behaviour/adaptivenopenalty/behaviour.php
@@ -38,8 +38,6 @@ require_once(dirname(__FILE__) . '/../adaptive/behaviour.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qbehaviour_adaptivenopenalty extends qbehaviour_adaptive {
- const IS_ARCHETYPAL = true;
-
protected function adjusted_fraction($fraction, $prevtries) {
return $fraction;
}
diff --git a/question/behaviour/adaptivenopenalty/behaviourtype.php b/question/behaviour/adaptivenopenalty/behaviourtype.php
new file mode 100644
index 00000000000..2fd6eff0b24
--- /dev/null
+++ b/question/behaviour/adaptivenopenalty/behaviourtype.php
@@ -0,0 +1,38 @@
+.
+
+/**
+ * Question behaviour type for adaptive behaviour, without penalties.
+ *
+ * @package qbehaviour_adaptivenopenalty
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(dirname(__FILE__) . '/../adaptive/behaviourtype.php');
+
+
+/**
+ * Question behaviour type information for adaptive behaviour, without penalties.
+ *
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qbehaviour_adaptivenopenalty_type extends qbehaviour_adaptive_type {
+}
diff --git a/question/behaviour/behaviourbase.php b/question/behaviour/behaviourbase.php
index d8f4b2a1555..b3b476a59f8 100644
--- a/question/behaviour/behaviourbase.php
+++ b/question/behaviour/behaviourbase.php
@@ -15,7 +15,7 @@
// along with Moodle. If not, see .
/**
- * Defines the quetsion behaviour base class
+ * Defines the question behaviour base class
*
* @package moodlecore
* @subpackage questionbehaviours
@@ -40,21 +40,10 @@ defined('MOODLE_INTERNAL') || die();
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class question_behaviour {
- /**
- * Certain behaviours are definitive of a way that questions can
- * behave when attempted. For example deferredfeedback model, interactive
- * model, etc. These are the options that should be listed in the
- * user-interface. These models should define the class constant
- * IS_ARCHETYPAL as true. Other models are more implementation details, for
- * example the informationitem model, or a special subclass like
- * interactive_adapted_for_my_qtype. These models should IS_ARCHETYPAL as
- * false.
- * @var boolean
- */
- const IS_ARCHETYPAL = false;
/** @var question_attempt the question attempt we are managing. */
protected $qa;
+
/** @var question_definition shortcut to $qa->get_question(). */
protected $question;
@@ -95,16 +84,6 @@ abstract class question_behaviour {
return substr(get_class($this), 11);
}
- /**
- * 'Override' this method if there are some display options that do not make
- * sense 'during the attempt'.
- * @return array of {@link question_display_options} field names, that are
- * not relevant to this behaviour before a 'finish' action.
- */
- public static function get_unused_display_options() {
- return array();
- }
-
/**
* Cause the question to be renderered. This gets the appropriate behaviour
* renderer using {@link get_renderer()}, and adjusts the display
@@ -181,24 +160,25 @@ abstract class question_behaviour {
/**
* What is the minimum fraction that can be scored for this question.
* Normally this will be based on $this->question->get_min_fraction(),
- * but may be modified in some way by the model.
+ * but may be modified in some way by the behaviour.
*
* @return number the minimum fraction when this question is attempted under
- * this model.
+ * this behaviour.
*/
public function get_min_fraction() {
return 0;
}
/**
- * Adjust a random guess score for a question using this model. You have to
- * do this without knowing details of the specific question, or which usage
- * it is in.
- * @param number $fraction the random guess score from the question type.
- * @return number the adjusted fraction.
+ * Return the maximum possible fraction that can be scored for this question.
+ * Normally this will be based on $this->question->get_max_fraction(),
+ * but may be modified in some way by the behaviour.
+ *
+ * @return number the maximum fraction when this question is attempted under
+ * this behaviour.
*/
- public static function adjust_random_guess_score($fraction) {
- return $fraction;
+ public function get_max_fraction() {
+ return $this->question->get_max_fraction();
}
/**
@@ -464,7 +444,7 @@ abstract class question_behaviour {
$pendingstep->get_behaviour_var('maxmark');
if ($pendingstep->get_behaviour_var('mark') === '') {
$fraction = null;
- } else if ($fraction > 1 || $fraction < $this->qa->get_min_fraction()) {
+ } else if ($fraction > $this->qa->get_max_fraction() || $fraction < $this->qa->get_min_fraction()) {
throw new coding_exception('Score out of range when processing ' .
'a manual grading action.', 'Question ' . $this->question->id .
', slot ' . $this->qa->get_slot() . ', fraction ' . $fraction);
@@ -477,20 +457,6 @@ abstract class question_behaviour {
return question_attempt::KEEP;
}
- /**
- * Validate that the manual grade submitted for a particular question is in range.
- * @param int $qubaid the question_usage id.
- * @param int $slot the slot number within the usage.
- * @return bool whether the submitted data is in range.
- */
- public static function is_manual_grade_in_range($qubaid, $slot) {
- $prefix = 'q' . $qubaid . ':' . $slot . '_';
- $mark = question_utils::optional_param_mark($prefix . '-mark');
- $maxmark = optional_param($prefix . '-maxmark', null, PARAM_FLOAT);
- $minfraction = optional_param($prefix . ':minfraction', null, PARAM_FLOAT);
- return is_null($mark) || ($mark >= $minfraction * $maxmark && $mark <= $maxmark);
- }
-
/**
* @param $comment the comment text to format. If omitted,
* $this->qa->get_manual_comment() is used.
@@ -662,7 +628,7 @@ abstract class question_behaviour_with_save extends question_behaviour {
/**
* This helper class contains the constants and methods required for
- * manipulating scores for certainly based marking.
+ * manipulating scores for certainty based marking.
*
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -677,16 +643,29 @@ abstract class question_cbm {
/** @var array list of all the certainty levels. */
public static $certainties = array(self::LOW, self::MED, self::HIGH);
- /**#@+ @var array coefficients used to adjust the fraction based on certainty.. */
- protected static $factor = array(
- self::LOW => 0.333333333333333,
- self::MED => 1.333333333333333,
+ /**#@+ @var array coefficients used to adjust the fraction based on certainty. */
+ protected static $rightscore = array(
+ self::LOW => 1,
+ self::MED => 2,
self::HIGH => 3,
);
- protected static $offset = array(
- self::LOW => 0,
- self::MED => -0.666666666666667,
- self::HIGH => -2,
+ protected static $wrongscore = array(
+ self::LOW => 0,
+ self::MED => -2,
+ self::HIGH => -6,
+ );
+ /**#@-*/
+
+ /**#@+ @var array upper and lower limits of the optimal window. */
+ protected static $lowlimit = array(
+ self::LOW => 0,
+ self::MED => 0.666666666666667,
+ self::HIGH => 0.8,
+ );
+ protected static $highlimit = array(
+ self::LOW => 0.666666666666667,
+ self::MED => 0.8,
+ self::HIGH => 1,
);
/**#@-*/
@@ -699,27 +678,68 @@ abstract class question_cbm {
}
/**
- * Given a fraction, and a certainly, compute the adjusted fraction.
+ * Given a fraction, and a certainty, compute the adjusted fraction.
* @param number $fraction the raw fraction for this question.
- * @param int $certainty one of the certainly level constants.
- * @return number the adjusted fraction taking the certainly into account.
+ * @param int $certainty one of the certainty level constants.
+ * @return number the adjusted fraction taking the certainty into account.
*/
public static function adjust_fraction($fraction, $certainty) {
- return self::$offset[$certainty] + self::$factor[$certainty] * $fraction;
+ if ($certainty == -1) {
+ // Certainty -1 has never been used in standard Moodle, but is
+ // used in Tony-Gardiner Medwin's patches to mean 'No idea' which
+ // we intend to implement: MDL-42077. In the mean time, avoid
+ // errors for people who have used TGM's patches.
+ return 0;
+ }
+ if ($fraction <= 0.00000005) {
+ return self::$wrongscore[$certainty];
+ } else {
+ return self::$rightscore[$certainty] * $fraction;
+ }
}
/**
* @param int $certainty one of the LOW/MED/HIGH constants.
- * @return string a textual desciption of this certainly.
+ * @return string a textual description of this certainty.
*/
public static function get_string($certainty) {
return get_string('certainty' . $certainty, 'qbehaviour_deferredcbm');
}
+ /**
+ * @param int $certainty one of the LOW/MED/HIGH constants.
+ * @return string a short textual description of this certainty.
+ */
+ public static function get_short_string($certainty) {
+ return get_string('certaintyshort' . $certainty, 'qbehaviour_deferredcbm');
+ }
+
+ /**
+ * Add information about certainty to a response summary.
+ * @param string $summary the response summary.
+ * @param int $certainty the level of certainty to add.
+ * @return string the summary with information about the certainty added.
+ */
public static function summary_with_certainty($summary, $certainty) {
if (is_null($certainty)) {
return $summary;
}
- return $summary . ' [' . self::get_string($certainty) . ']';
+ return $summary . ' [' . self::get_short_string($certainty) . ']';
+ }
+
+ /**
+ * @param int $certainty one of the LOW/MED/HIGH constants.
+ * @return float the lower limit of the optimal probability range for this certainty.
+ */
+ public static function optimal_probablility_low($certainty) {
+ return self::$lowlimit[$certainty];
+ }
+
+ /**
+ * @param int $certainty one of the LOW/MED/HIGH constants.
+ * @return float the upper limit of the optimal probability range for this certainty.
+ */
+ public static function optimal_probablility_high($certainty) {
+ return self::$highlimit[$certainty];
}
}
diff --git a/question/behaviour/behaviourtypebase.php b/question/behaviour/behaviourtypebase.php
new file mode 100644
index 00000000000..c4684d79d0a
--- /dev/null
+++ b/question/behaviour/behaviourtypebase.php
@@ -0,0 +1,144 @@
+.
+
+/**
+ * Defines the question behaviour type base class
+ *
+ * @package core
+ * @subpackage questionbehaviours
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * This class represents the type of behaviour, rather than the instance of the
+ * behaviour which control a particular question attempt.
+ *
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class question_behaviour_type {
+ /**
+ * Certain behaviours are definitive of a way that questions can behave when
+ * attempted. For example deferredfeedback model, interactive model, etc.
+ * These are the options that should be listed in the user-interface, and
+ * for these behaviours this method should return true. Other behaviours are
+ * more implementation details, for example the informationitem behaviours,
+ * or a special subclass like interactive_adapted_for_my_qtype. These
+ * behaviours should return false.
+ * @return bool whether this is an archetypal behaviour.
+ */
+ public function is_archetypal() {
+ return false;
+ }
+
+ /**
+ * Override this method if there are some display options that do not make
+ * sense 'during the attempt'.
+ * @return array of {@link question_display_options} field names, that are
+ * not relevant to this behaviour before a 'finish' action.
+ */
+ public function get_unused_display_options() {
+ return array();
+ }
+
+ /**
+ * Adjust a random guess score for a question using this model. You have to
+ * do this without knowing details of the specific question, or which usage
+ * it is in.
+ * @param number $fraction the random guess score from the question type.
+ * @return number the adjusted fraction.
+ */
+ public function adjust_random_guess_score($fraction) {
+ return $fraction;
+ }
+
+ /**
+ * Get summary information about a queston usage.
+ *
+ * Behaviours are not obliged to do anything here, but this is an opportunity
+ * to provide additional information that can be displayed in places like
+ * at the top of the quiz review page.
+ *
+ * In the return value, the array keys should be identifiers of the form
+ * qbehaviour_behaviourname_meaningfullkey. For qbehaviour_deferredcbm_highsummary.
+ * The values should be arrays with two items, title and content. Each of these
+ * should be either a string, or a renderable.
+ *
+ * To understand how to implement this method, look at the CBM behaviours,
+ * and their unit tests.
+ *
+ * @param question_usage_by_activity $quba the usage to provide summary data for.
+ * @return array as described above.
+ */
+ public function summarise_usage(question_usage_by_activity $quba,
+ question_display_options $options) {
+ return array();
+ }
+}
+
+
+/**
+ * This class exists to allow behaviours that worked in Moodle 2.3 to continue
+ * to work. It implements the question_behaviour_type API for the other behaviour
+ * as much as possible in a backwards-compatible way.
+ *
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class question_behaviour_type_fallback extends question_behaviour_type {
+
+ /** @var string the behaviour class name. */
+ protected $behaviourclass;
+
+ /**
+ * @param string $behaviourtype the type of behaviour we are providing a fallback for.
+ */
+ public function __construct($behaviour) {
+ question_engine::load_behaviour_class($behaviour);
+ $this->behaviourclass = 'qbehaviour_' . $behaviour;
+ }
+
+ public function is_archetypal() {
+ return constant($this->behaviourclass . '::IS_ARCHETYPAL');
+ }
+
+ /**
+ * Override this method if there are some display options that do not make
+ * sense 'during the attempt'.
+ * @return array of {@link question_display_options} field names, that are
+ * not relevant to this behaviour before a 'finish' action.
+ */
+ public function get_unused_display_options() {
+ return call_user_func(array($this->behaviourclass, 'get_unused_display_options'));
+ }
+
+ /**
+ * Adjust a random guess score for a question using this model. You have to
+ * do this without knowing details of the specific question, or which usage
+ * it is in.
+ * @param number $fraction the random guess score from the question type.
+ * @return number the adjusted fraction.
+ */
+ public function adjust_random_guess_score($fraction) {
+ return call_user_func(array($this->behaviourclass, 'adjust_random_guess_score'),
+ $fraction);
+ }
+}
diff --git a/question/behaviour/deferredcbm/behaviour.php b/question/behaviour/deferredcbm/behaviour.php
index f1aa74fef76..04abd7a44e7 100644
--- a/question/behaviour/deferredcbm/behaviour.php
+++ b/question/behaviour/deferredcbm/behaviour.php
@@ -16,7 +16,7 @@
/**
* Question behaviour that is like the deferred feedback model, but with
- * certainly based marking. That is, in addition to the other controls, there are
+ * certainty based marking. That is, in addition to the other controls, there are
* where the student can indicate how certain they are that their answer is right.
*
* @package qbehaviour
@@ -45,13 +45,12 @@ require_once(dirname(__FILE__) . '/../deferredfeedback/behaviour.php');
class qbehaviour_deferredcbm extends qbehaviour_deferredfeedback {
const IS_ARCHETYPAL = true;
- public static function get_unused_display_options() {
- return array('correctness', 'marks', 'specificfeedback', 'generalfeedback',
- 'rightanswer');
+ public function get_min_fraction() {
+ return question_cbm::adjust_fraction(0, question_cbm::HIGH);
}
- public function get_min_fraction() {
- return question_cbm::adjust_fraction(parent::get_min_fraction(), question_cbm::HIGH);
+ public function get_max_fraction() {
+ return question_cbm::adjust_fraction(1, question_cbm::HIGH);
}
public function get_expected_data() {
@@ -63,7 +62,7 @@ class qbehaviour_deferredcbm extends qbehaviour_deferredfeedback {
public function get_right_answer_summary() {
$summary = parent::get_right_answer_summary();
- return $summary . ' [' . question_cbm::get_string(question_cbm::HIGH) . ']';
+ return question_cbm::summary_with_certainty($summary, question_cbm::HIGH);
}
public function get_correct_response() {
@@ -122,8 +121,4 @@ class qbehaviour_deferredcbm extends qbehaviour_deferredfeedback {
}
return $summary;
}
-
- public static function adjust_random_guess_score($fraction) {
- return question_cbm::adjust_fraction($fraction, question_cbm::default_certainty());
- }
}
diff --git a/question/behaviour/deferredcbm/behaviourtype.php b/question/behaviour/deferredcbm/behaviourtype.php
new file mode 100644
index 00000000000..44cf330a5bb
--- /dev/null
+++ b/question/behaviour/deferredcbm/behaviourtype.php
@@ -0,0 +1,241 @@
+.
+
+/**
+ * Question behaviour type for deferred feedback with CBM behaviour.
+ *
+ * @package qbehaviour_deferredcbm
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(dirname(__FILE__) . '/../deferredfeedback/behaviourtype.php');
+
+
+/**
+ * Question behaviour type information for deferred feedback with CBM behaviour.
+ *
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qbehaviour_deferredcbm_type extends qbehaviour_deferredfeedback_type {
+ public function adjust_random_guess_score($fraction) {
+ return question_cbm::adjust_fraction($fraction, question_cbm::default_certainty());
+ }
+
+ public function summarise_usage(question_usage_by_activity $quba, question_display_options $options) {
+ $summarydata = parent::summarise_usage($quba, $options);
+
+ if ($options->marks < question_display_options::MARK_AND_MAX) {
+ return $summarydata;
+ }
+
+ // Prepare accumulators to hold the data we are about to collect.
+ $notansweredcount = 0;
+ $notansweredweight = 0;
+ $attemptcount = array(
+ question_cbm::HIGH => 0,
+ question_cbm::MED => 0,
+ question_cbm::LOW => 0,
+ );
+ $totalweight = array(
+ question_cbm::HIGH => 0,
+ question_cbm::MED => 0,
+ question_cbm::LOW => 0,
+ );
+ $totalrawscore = array(
+ question_cbm::HIGH => 0,
+ question_cbm::MED => 0,
+ question_cbm::LOW => 0,
+ );
+ $totalcbmscore = array(
+ question_cbm::HIGH => 0,
+ question_cbm::MED => 0,
+ question_cbm::LOW => 0,
+ );
+
+ // Loop through the data, and add it to the accumulators.
+ foreach ($quba->get_attempt_iterator() as $qa) {
+ if (strpos($qa->get_behaviour_name(), 'cbm') === false || $qa->get_max_mark() < 0.0000005) {
+ continue;
+ }
+
+ $gradedstep = $qa->get_last_step_with_behaviour_var('_rawfraction');
+
+ if (!$gradedstep->has_behaviour_var('_rawfraction')) {
+ $notansweredcount += 1;
+ $notansweredweight += $qa->get_max_mark();
+ continue;
+ }
+
+ $certainty = $qa->get_last_behaviour_var('certainty');
+ if (is_null($certainty) || $certainty == -1) {
+ // Certainty -1 has never been used in standard Moodle, but is
+ // used in Tony-Gardiner Medwin's patches to mean 'No idea' which
+ // we intend to implement: MDL-42077. In the mean time, avoid
+ // errors for people who have used TGM's patches.
+ $certainty = question_cbm::default_certainty();
+ }
+
+ $attemptcount[$certainty] += 1;
+ $totalweight[$certainty] += $qa->get_max_mark();
+ $totalrawscore[$certainty] += $qa->get_max_mark() * $gradedstep->get_behaviour_var('_rawfraction');
+ $totalcbmscore[$certainty] += $qa->get_mark();
+ }
+
+ // Hence compute some statistics.
+ $totalquestions = $notansweredcount + array_sum($attemptcount);
+ $grandtotalweight = $notansweredweight + array_sum($totalweight);
+ $accuracy = array_sum($totalrawscore) / $grandtotalweight;
+ $averagecbm = array_sum($totalcbmscore) / $grandtotalweight;
+ $cbmbonus = $this->calculate_bonus($averagecbm, $accuracy);
+ $accuracyandbonus = $accuracy + $cbmbonus;
+
+ // Now we can start generating some of the summary: overall values.
+ $summarydata['qbehaviour_cbm_entire_quiz_heading'] = array(
+ 'title' => '',
+ 'content' => html_writer::tag('h3',
+ get_string('forentirequiz', 'qbehaviour_deferredcbm', $totalquestions),
+ array('class' => 'qbehaviour_deferredcbm_summary_heading')),
+ );
+ $summarydata['qbehaviour_cbm_entire_quiz_cbm_average'] = array(
+ 'title' => get_string('averagecbmmark', 'qbehaviour_deferredcbm'),
+ 'content' => format_float($averagecbm, $options->markdp),
+ );
+ $summarydata['qbehaviour_cbm_entire_quiz_accuracy'] = array(
+ 'title' => get_string('accuracy', 'qbehaviour_deferredcbm'),
+ 'content' => $this->format_probability($accuracy, 1),
+ );
+ $summarydata['qbehaviour_cbm_entire_quiz_cbm_bonus'] = array(
+ 'title' => get_string('cbmbonus', 'qbehaviour_deferredcbm'),
+ 'content' => $this->format_probability($cbmbonus, 1),
+ );
+ $summarydata['qbehaviour_cbm_entire_quiz_accuracy_and_bonus'] = array(
+ 'title' => get_string('accuracyandbonus', 'qbehaviour_deferredcbm'),
+ 'content' => $this->format_probability($accuracyandbonus, 1),
+ );
+
+ if ($notansweredcount && array_sum($attemptcount) > 0) {
+ $totalquestions = array_sum($attemptcount);
+ $grandtotalweight = array_sum($totalweight);
+ $accuracy = array_sum($totalrawscore) / $grandtotalweight;
+ $averagecbm = array_sum($totalcbmscore) / $grandtotalweight;
+ $cbmbonus = $this->calculate_bonus($averagecbm, $accuracy);
+ $accuracyandbonus = $accuracy + $cbmbonus;
+
+ $summarydata['qbehaviour_cbm_answered_quiz_heading'] = array(
+ 'title' => '',
+ 'content' => html_writer::tag('h3',
+ get_string('foransweredquestions', 'qbehaviour_deferredcbm', $totalquestions),
+ array('class' => 'qbehaviour_deferredcbm_summary_heading')),
+ );
+ $summarydata['qbehaviour_cbm_answered_quiz_cbm_average'] = array(
+ 'title' => get_string('averagecbmmark', 'qbehaviour_deferredcbm'),
+ 'content' => format_float($averagecbm, $options->markdp),
+ );
+ $summarydata['qbehaviour_cbm_answered_quiz_accuracy'] = array(
+ 'title' => get_string('accuracy', 'qbehaviour_deferredcbm'),
+ 'content' => $this->format_probability($accuracy, 1),
+ );
+ $summarydata['qbehaviour_cbm_answered_quiz_cbm_bonus'] = array(
+ 'title' => get_string('cbmbonus', 'qbehaviour_deferredcbm'),
+ 'content' => $this->format_probability($cbmbonus, 1),
+ );
+ $summarydata['qbehaviour_cbm_answered_quiz_accuracy_and_bonus'] = array(
+ 'title' => get_string('accuracyandbonus', 'qbehaviour_deferredcbm'),
+ 'content' => $this->format_probability($accuracyandbonus, 1),
+ );
+ }
+
+ // Now per-certainty level values.
+ $summarydata['qbehaviour_cbm_judgement_heading'] = array(
+ 'title' => '',
+ 'content' => html_writer::tag('h3', get_string('breakdownbycertainty', 'qbehaviour_deferredcbm'),
+ array('class' => 'qbehaviour_deferredcbm_summary_heading')),
+ );
+
+ foreach ($attemptcount as $certainty => $count) {
+ $key = 'qbehaviour_cbm_judgement' . $certainty;
+ $title = question_cbm::get_short_string($certainty);
+
+ if ($count == 0) {
+ $summarydata[$key] = array(
+ 'title' => $title,
+ 'content' => get_string('noquestions', 'qbehaviour_deferredcbm'),
+ );
+ continue;
+ }
+
+ $lowerlimit = question_cbm::optimal_probablility_low($certainty);
+ $upperlimit = question_cbm::optimal_probablility_high($certainty);
+ $fraction = $totalrawscore[$certainty] / $totalweight[$certainty];
+
+ $a = new stdClass();
+ $a->responses = $count;
+ $a->idealrangelow = $this->format_probability($lowerlimit);
+ $a->idealrangehigh = $this->format_probability($upperlimit);
+ $a->fraction = html_writer::tag('span', $this->format_probability($fraction),
+ array('class' => 'qbehaviour_deferredcbm_actual_percentage'));
+
+ if ($fraction < $lowerlimit - 0.0000005) {
+ if ((pow($fraction - $lowerlimit, 2) * $count) > 0.5) { // Rough indicator of significance: t > 1.5 or 1.8.
+ $judgement = 'overconfident';
+ } else {
+ $judgement = 'slightlyoverconfident';
+ }
+ } else if ($fraction > $upperlimit + 0.0000005) {
+ if ((pow($fraction - $upperlimit, 2) * $count) > 0.5) {
+ $judgement = 'underconfident';
+ } else {
+ $judgement = 'slightlyunderconfident';
+ }
+ } else {
+ $judgement = 'judgementok';
+ }
+ $a->judgement = html_writer::tag('span', get_string($judgement, 'qbehaviour_deferredcbm'),
+ array('class' => 'qbehaviour_deferredcbm_' . $judgement));
+
+ $summarydata[$key] = array(
+ 'title' => $title,
+ 'content' => get_string('judgementsummary', 'qbehaviour_deferredcbm', $a),
+ );
+ }
+
+ return $summarydata;
+ }
+
+ protected function format_probability($probability, $dp = 0) {
+ return format_float($probability * 100, $dp) . '%';
+ }
+
+ public function calculate_bonus($total, $accuracy) {
+ $expectedforaccuracy = max(
+ $accuracy * question_cbm::adjust_fraction(1, question_cbm::LOW) +
+ (1 - $accuracy) * question_cbm::adjust_fraction(0, question_cbm::LOW),
+ $accuracy * question_cbm::adjust_fraction(1, question_cbm::MED) +
+ (1 - $accuracy) * question_cbm::adjust_fraction(0, question_cbm::MED),
+ $accuracy * question_cbm::adjust_fraction(1, question_cbm::HIGH) +
+ (1 - $accuracy) * question_cbm::adjust_fraction(0, question_cbm::HIGH)
+ );
+ // The constant 0.1 here is determinted empirically from looking at lots
+ // for CBM quiz results. See www.ucl.ac.uk/~ucgbarg/tea/IUPS_2013a.pdf.
+ // It approximately maximises the reliability of accuracy + bonus.
+ return 0.1 * ($total - $expectedforaccuracy);
+ }
+}
diff --git a/question/behaviour/deferredcbm/lang/en/qbehaviour_deferredcbm.php b/question/behaviour/deferredcbm/lang/en/qbehaviour_deferredcbm.php
index 7a78f17edfd..8d679e5999b 100644
--- a/question/behaviour/deferredcbm/lang/en/qbehaviour_deferredcbm.php
+++ b/question/behaviour/deferredcbm/lang/en/qbehaviour_deferredcbm.php
@@ -23,10 +23,43 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+$string['accuracy'] = 'Accuracy';
+$string['accuracyandbonus'] = 'Accuracy + Bonus';
$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 80%)';
-$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}.';
+$string['averagecbmmark'] = 'Average CBM mark';
+$string['basemark'] = 'Base mark {$a}';
+$string['breakdownbycertainty'] = 'Break-down by certainty';
+$string['cbmbonus'] = 'CBM bonus';
+$string['cbmmark'] = 'CBM mark {$a}';
+$string['certainty'] = 'Certainty';
+$string['certainty_help'] = 'Certainty-based marking requires you to indicate how reliable you think your answer is. The available levels are:
+
+Certainty level | C=1 (Unsure) | C=2 (Mid) | C=3 (Quite sure)
+------------------- | ------------ | --------- | ----------------
+Mark if correct | 1 | 2 | 3
+Mark if wrong | 0 | -2 | -6
+Probability correct | <67% | 67-80% | >80%
+
+Best marks are gained by acknowledging uncertainty. For example, if you think there is more than a 1 in 3 chance of being wrong, you should enter C=1 and avoid the risk of a negative mark.
+';
+$string['certainty_link'] = 'qbehaviour/deferredcbm/certainty';
+$string['certainty-1'] = 'No Idea';
+$string['certainty1'] = 'C=1 (Unsure: <67%)';
+$string['certainty2'] = 'C=2 (Mid: >67%)';
+$string['certainty3'] = 'C=3 (Quite sure: >80%)';
+$string['certaintyshort-1'] = 'No Idea';
+$string['certaintyshort1'] = 'C=1';
+$string['certaintyshort2'] = 'C=2';
+$string['certaintyshort3'] = 'C=3';
+$string['dontknow'] = 'No idea';
+$string['foransweredquestions'] = 'For just the {$a} answered questions';
+$string['forentirequiz'] = 'For the entire quiz ({$a} questions)';
+$string['judgementok'] = 'OK';
+$string['judgementsummary'] = 'Responses: {$a->responses}. Accuracy: {$a->fraction}. (Optimal range {$a->idealrangelow} to {$a->idealrangehigh}). You were {$a->judgement} using this certainty level.';
+$string['howcertainareyou'] = 'Certainty{$a->help}: {$a->choices}';
+$string['noquestions'] = 'No responses';
+$string['overconfident'] = 'over-confident';
$string['pluginname'] = 'Deferred feedback with CBM';
+$string['slightlyoverconfident'] = 'a bit over-confident';
+$string['slightlyunderconfident'] = 'a bit under-confident';
+$string['underconfident'] = 'under-confident';
diff --git a/question/behaviour/deferredcbm/renderer.php b/question/behaviour/deferredcbm/renderer.php
index 8cb33d42ca4..f5abbf58e14 100644
--- a/question/behaviour/deferredcbm/renderer.php
+++ b/question/behaviour/deferredcbm/renderer.php
@@ -36,7 +36,7 @@ defined('MOODLE_INTERNAL') || die();
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qbehaviour_deferredcbm_renderer extends qbehaviour_renderer {
- protected function certainly_choices($controlname, $selected, $readonly) {
+ protected function certainty_choices($controlname, $selected, $readonly) {
$attributes = array(
'type' => 'radio',
'name' => $controlname,
@@ -55,17 +55,20 @@ class qbehaviour_deferredcbm_renderer extends qbehaviour_renderer {
} else {
unset($attributes['checked']);
}
- $choices .= ' ' . html_writer::empty_tag('input', $attributes) . ' ' .
- html_writer::tag('label', question_cbm::get_string($certainty),
- array('for' => $id));
+ $choices .= ' ' .
+ html_writer::tag('label', html_writer::empty_tag('input', $attributes) .
+ question_cbm::get_string($certainty), array('for' => $id));
}
return $choices;
}
public function controls(question_attempt $qa, question_display_options $options) {
- return html_writer::tag('div', get_string('howcertainareyou', 'qbehaviour_deferredcbm',
- $this->certainly_choices($qa->get_behaviour_field_name('certainty'),
- $qa->get_last_behaviour_var('certainty'), $options->readonly)),
+ $a = new stdClass();
+ $a->help = $this->output->help_icon('certainty', 'qbehaviour_deferredcbm');
+ $a->choices = $this->certainty_choices($qa->get_behaviour_field_name('certainty'),
+ $qa->get_last_behaviour_var('certainty'), $options->readonly);
+
+ return html_writer::tag('div', get_string('howcertainareyou', 'qbehaviour_deferredcbm', $a),
array('class' => 'certaintychoices'));
}
@@ -87,15 +90,18 @@ class qbehaviour_deferredcbm_renderer extends qbehaviour_renderer {
question_cbm::get_string($qa->get_last_behaviour_var('_assumedcertainty'))));
}
- if ($options->marks >= question_display_options::MARK_AND_MAX) {
- $a = new stdClass();
- $a->rawmark = format_float($qa->get_last_behaviour_var('_rawfraction') *
- $qa->get_max_mark(), $options->markdp);
- $a->mark = $qa->format_mark($options->markdp);
- $feedback .= html_writer::tag('p',
- get_string('markadjustment', 'qbehaviour_deferredcbm', $a));
- }
-
return $feedback;
}
+
+ public function marked_out_of_max(question_attempt $qa, core_question_renderer $qoutput,
+ question_display_options $options) {
+ return get_string('basemark', 'qbehaviour_deferredcbm', $qa->format_fraction_as_mark(
+ question_cbm::adjust_fraction(1, question_cbm::default_certainty()),
+ $options->markdp));
+ }
+
+ public function mark_out_of_max(question_attempt $qa, core_question_renderer $qoutput,
+ question_display_options $options) {
+ return get_string('cbmmark', 'qbehaviour_deferredcbm', $qa->format_mark($options->markdp));
+ }
}
\ No newline at end of file
diff --git a/question/behaviour/deferredcbm/styles.css b/question/behaviour/deferredcbm/styles.css
new file mode 100644
index 00000000000..0e063dc7daf
--- /dev/null
+++ b/question/behaviour/deferredcbm/styles.css
@@ -0,0 +1,29 @@
+.qbehaviour_deferredcbm_slightlyunderconfident,
+.qbehaviour_deferredcbm_slightlyoverconfident {
+ font-weight: bold;
+ color: #600;
+}
+.qbehaviour_deferredcbm_underconfident,
+.qbehaviour_deferredcbm_overconfident {
+ font-weight: bold;
+ color: #c00;
+}
+
+.qbehaviour_deferredcbm_judgementok {
+ font-weight: bold;
+ color: #080;
+}
+
+.qbehaviour_deferredcbm_actual_percentage {
+ font-weight: bold;
+}
+
+.qbehaviour_deferredcbm_summary_heading {
+ margin: 0;
+}
+.que.deferredcbm .certaintychoices input[type="radio"] {
+ margin-left: 0.5em;
+}
+.que.deferredcbm .certaintychoices label {
+ white-space: nowrap;
+}
diff --git a/question/behaviour/deferredcbm/tests/behaviourtype_test.php b/question/behaviour/deferredcbm/tests/behaviourtype_test.php
new file mode 100644
index 00000000000..d0f44c08e85
--- /dev/null
+++ b/question/behaviour/deferredcbm/tests/behaviourtype_test.php
@@ -0,0 +1,142 @@
+.
+
+/**
+ * This file contains tests that walks a question through the deferred feedback
+ * with certainty base marking behaviour.
+ *
+ * @package qbehaviour
+ * @subpackage deferredcbm
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once(dirname(__FILE__) . '/../../../engine/lib.php');
+require_once(dirname(__FILE__) . '/../../../engine/tests/helpers.php');
+
+
+/**
+ * Unit tests for the deferred feedback with certainty base marking behaviour.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qbehaviour_deferredcbm_type_test extends qbehaviour_walkthrough_test_base {
+
+ /** @var qbehaviour_deferredcbm_type */
+ protected $behaviourtype;
+
+ public function setUp() {
+ parent::setUp();
+ $this->behaviourtype = question_engine::get_behaviour_type('deferredcbm');
+ }
+
+ public function test_is_archetypal() {
+ $this->assertTrue($this->behaviourtype->is_archetypal());
+ }
+
+ public function test_get_unused_display_options() {
+ $this->assertEquals(array('correctness', 'marks', 'specificfeedback', 'generalfeedback', 'rightanswer'),
+ $this->behaviourtype->get_unused_display_options());
+ }
+
+ public function test_adjust_random_guess_score() {
+ $this->assertEquals(0, $this->behaviourtype->adjust_random_guess_score(0));
+ $this->assertEquals(1, $this->behaviourtype->adjust_random_guess_score(1));
+ }
+
+ public function test_summarise_usage_max_mark_1() {
+
+ // Create a usage comprising 3 true-false questions.
+ $this->quba->set_preferred_behaviour('deferredcbm');
+ $this->quba->add_question(test_question_maker::make_question('truefalse', 'true'), 3);
+ $this->quba->add_question(test_question_maker::make_question('truefalse', 'true'), 3);
+ $this->quba->add_question(test_question_maker::make_question('truefalse', 'true'), 3);
+ $this->quba->start_all_questions();
+
+ // Process responses right, high certainty; right, med certainty; wrong, med certainty.
+ $this->quba->process_action(1, array('answer' => 1, '-certainty' => 3));
+ $this->quba->process_action(2, array('answer' => 1, '-certainty' => 2));
+ $this->quba->process_action(3, array('answer' => 0, '-certainty' => 2));
+ $this->quba->finish_all_questions();
+
+ // Get the summary.
+ $summarydata = $this->quba->get_summary_information(new question_display_options());
+
+ // Verify.
+ $this->assertContains(get_string('breakdownbycertainty', 'qbehaviour_deferredcbm'),
+ $summarydata['qbehaviour_cbm_judgement_heading']['content']);
+
+ $this->assertContains('100%',
+ $summarydata['qbehaviour_cbm_judgement3']['content']);
+ $this->assertContains(get_string('judgementok', 'qbehaviour_deferredcbm'),
+ $summarydata['qbehaviour_cbm_judgement3']['content']);
+
+ $this->assertContains('50%',
+ $summarydata['qbehaviour_cbm_judgement2']['content']);
+ $this->assertContains(get_string('slightlyoverconfident', 'qbehaviour_deferredcbm'),
+ $summarydata['qbehaviour_cbm_judgement2']['content']);
+
+ $this->assertContains(get_string('noquestions', 'qbehaviour_deferredcbm'),
+ $summarydata['qbehaviour_cbm_judgement1']['content']);
+ }
+
+ public function test_summarise_usage_max_mark_3() {
+
+ // Create a usage comprising 3 true-false questions.
+ $this->quba->set_preferred_behaviour('deferredcbm');
+ $this->quba->add_question(test_question_maker::make_question('truefalse', 'true'), 1);
+ $this->quba->add_question(test_question_maker::make_question('truefalse', 'true'), 1);
+ $this->quba->add_question(test_question_maker::make_question('truefalse', 'true'), 1);
+ $this->quba->start_all_questions();
+
+ // Process responses right, high certainty; right, med certainty; wrong, med certainty.
+ $this->quba->process_action(1, array('answer' => 1, '-certainty' => 3));
+ $this->quba->process_action(2, array('answer' => 1, '-certainty' => 2));
+ $this->quba->process_action(3, array('answer' => 0, '-certainty' => 2));
+ $this->quba->finish_all_questions();
+
+ // Get the summary.
+ $summarydata = $this->quba->get_summary_information(new question_display_options());
+
+ // Verify.
+ $this->assertContains(get_string('breakdownbycertainty', 'qbehaviour_deferredcbm'),
+ $summarydata['qbehaviour_cbm_judgement_heading']['content']);
+
+ $this->assertContains('100%',
+ $summarydata['qbehaviour_cbm_judgement3']['content']);
+ $this->assertContains(get_string('judgementok', 'qbehaviour_deferredcbm'),
+ $summarydata['qbehaviour_cbm_judgement3']['content']);
+
+ $this->assertContains('50%',
+ $summarydata['qbehaviour_cbm_judgement2']['content']);
+ $this->assertContains(get_string('slightlyoverconfident', 'qbehaviour_deferredcbm'),
+ $summarydata['qbehaviour_cbm_judgement2']['content']);
+
+ $this->assertContains(get_string('noquestions', 'qbehaviour_deferredcbm'),
+ $summarydata['qbehaviour_cbm_judgement1']['content']);
+ }
+
+ public function test_calculate_bonus() {
+ $this->assertEquals(0.05, $this->behaviourtype->calculate_bonus(1, 1/2));
+ $this->assertEquals(-0.01, $this->behaviourtype->calculate_bonus(2, 9/10));
+ $this->assertEquals(0, $this->behaviourtype->calculate_bonus(3, 1));
+ }
+}
diff --git a/question/behaviour/deferredcbm/tests/question_cbm_test.php b/question/behaviour/deferredcbm/tests/question_cbm_test.php
new file mode 100644
index 00000000000..f67025d62fe
--- /dev/null
+++ b/question/behaviour/deferredcbm/tests/question_cbm_test.php
@@ -0,0 +1,56 @@
+.
+
+/**
+ * This file contains tests that walks a question through the deferred feedback
+ * with certainty base marking behaviour.
+ *
+ * @package qbehaviour
+ * @subpackage deferredcbm
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once(dirname(__FILE__) . '/../../../engine/lib.php');
+
+
+/**
+ * Unit tests for the deferred feedback with certainty base marking behaviour.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qbehaviour_deferredcbm_cbm_test extends basic_testcase {
+
+ public function test_adjust_fraction() {
+ $this->assertEquals( 1, question_cbm::adjust_fraction( 1, question_cbm::LOW), '', 0.0000001);
+ $this->assertEquals( 2, question_cbm::adjust_fraction( 1, question_cbm::MED), '', 0.0000001);
+ $this->assertEquals( 3, question_cbm::adjust_fraction( 1, question_cbm::HIGH), '', 0.0000001);
+ $this->assertEquals( 0, question_cbm::adjust_fraction( 0, question_cbm::LOW), '', 0.0000001);
+ $this->assertEquals(-2, question_cbm::adjust_fraction( 0, question_cbm::MED), '', 0.0000001);
+ $this->assertEquals(-6, question_cbm::adjust_fraction( 0, question_cbm::HIGH), '', 0.0000001);
+ $this->assertEquals( 0.5, question_cbm::adjust_fraction( 0.5, question_cbm::LOW), '', 0.0000001);
+ $this->assertEquals( 1, question_cbm::adjust_fraction( 0.5, question_cbm::MED), '', 0.0000001);
+ $this->assertEquals( 1.5, question_cbm::adjust_fraction( 0.5, question_cbm::HIGH), '', 0.0000001);
+ $this->assertEquals( 0, question_cbm::adjust_fraction(-0.25, question_cbm::LOW), '', 0.0000001);
+ $this->assertEquals(-2, question_cbm::adjust_fraction(-0.25, question_cbm::MED), '', 0.0000001);
+ $this->assertEquals(-6, question_cbm::adjust_fraction(-0.25, question_cbm::HIGH), '', 0.0000001);
+ }
+}
diff --git a/question/behaviour/deferredcbm/tests/walkthrough_test.php b/question/behaviour/deferredcbm/tests/walkthrough_test.php
index e5fe92f1d61..9417dfe885a 100644
--- a/question/behaviour/deferredcbm/tests/walkthrough_test.php
+++ b/question/behaviour/deferredcbm/tests/walkthrough_test.php
@@ -90,18 +90,18 @@ class qbehaviour_deferredcbm_walkthrough_test extends qbehaviour_walkthrough_tes
// Verify.
$this->check_current_state(question_state::$gradedright);
- $this->check_current_mark(2);
+ $this->check_current_mark(6);
$this->check_current_output(
$this->get_contains_tf_true_radio_expectation(false, true),
$this->get_contains_cbm_radio_expectation(3, false, true),
$this->get_contains_correct_expectation());
// Process a manual comment.
- $this->manual_grade('Not good enough!', 1, FORMAT_HTML);
+ $this->manual_grade('Not good enough!', 5, FORMAT_HTML);
// Verify.
- $this->check_current_state(question_state::$mangrpartial);
- $this->check_current_mark(1);
+ $this->check_current_state(question_state::$mangrright);
+ $this->check_current_mark(5);
$this->check_current_output(new question_pattern_expectation('/' .
preg_quote('Not good enough!', '/') . '/'));
@@ -110,10 +110,10 @@ class qbehaviour_deferredcbm_walkthrough_test extends qbehaviour_walkthrough_tes
$this->quba->regrade_all_questions();
// Verify.
- $this->check_current_state(question_state::$mangrpartial);
- $this->check_current_mark(1);
+ $this->check_current_state(question_state::$mangrright);
+ $this->check_current_mark(5);
$autogradedstep = $this->get_step($this->get_step_count() - 2);
- $this->assertEquals($autogradedstep->get_fraction(), -2, '', 0.0000001);
+ $this->assertEquals(-6, $autogradedstep->get_fraction(), '', 0.0000001);
}
public function test_deferred_cbm_truefalse_low_certainty() {
@@ -147,11 +147,11 @@ class qbehaviour_deferredcbm_walkthrough_test extends qbehaviour_walkthrough_tes
// Verify.
$this->check_current_state(question_state::$gradedright);
- $this->check_current_mark(0.6666667);
+ $this->check_current_mark(2);
$this->check_current_output($this->get_contains_correct_expectation(),
$this->get_contains_cbm_radio_expectation(1, false, true));
$this->assertEquals(get_string('true', 'qtype_truefalse') . ' [' .
- question_cbm::get_string(1) . ']',
+ question_cbm::get_short_string(question_cbm::LOW) . ']',
$this->quba->get_response_summary($this->slot));
}
@@ -177,7 +177,7 @@ class qbehaviour_deferredcbm_walkthrough_test extends qbehaviour_walkthrough_tes
// Verify.
$qa = $this->quba->get_question_attempt($this->slot);
$this->check_current_state(question_state::$gradedright);
- $this->check_current_mark(0.6666667);
+ $this->check_current_mark(2);
$this->check_current_output($this->get_contains_correct_expectation(),
$this->get_contains_cbm_radio_expectation(1, false, false),
new question_pattern_expectation('/' . preg_quote(
@@ -194,7 +194,7 @@ class qbehaviour_deferredcbm_walkthrough_test extends qbehaviour_walkthrough_tes
$mc = test_question_maker::make_a_multichoice_single_question();
// Attempt it getting it wrong.
- $this->start_attempt_at_question($mc, 'deferredcbm', 3);
+ $this->start_attempt_at_question($mc, 'deferredcbm', 1);
$rightindex = $this->get_mc_right_answer_index($mc);
$wrongindex = ($rightindex + 1) % 3;
$this->process_submission(array('answer' => $wrongindex, '-certainty' => 2));
@@ -202,16 +202,16 @@ class qbehaviour_deferredcbm_walkthrough_test extends qbehaviour_walkthrough_tes
// Verify.
$this->check_current_state(question_state::$gradedwrong);
- $this->check_current_mark(-3.3333333);
+ $this->check_current_mark(-2);
$this->check_current_output(
$this->get_contains_mc_radio_expectation($wrongindex, false, true),
$this->get_contains_cbm_radio_expectation(2, false, true),
$this->get_contains_incorrect_expectation());
- $this->assertEquals('A [' . question_cbm::get_string(question_cbm::HIGH) . ']',
+ $this->assertEquals('A [' . question_cbm::get_short_string(question_cbm::HIGH) . ']',
$this->quba->get_right_answer_summary($this->slot));
$this->assertRegExp('/' . preg_quote($mc->questiontext, '/') . '/',
$this->quba->get_question_summary($this->slot));
- $this->assertRegExp('/(B|C) \[' . preg_quote(question_cbm::get_string(2), '/') . '\]/',
+ $this->assertRegExp('/(B|C) \[' . preg_quote(question_cbm::get_short_string(question_cbm::MED), '/') . '\]/',
$this->quba->get_response_summary($this->slot));
// Save the old attempt.
@@ -220,7 +220,7 @@ class qbehaviour_deferredcbm_walkthrough_test extends qbehaviour_walkthrough_tes
// Reinitialise.
$this->setUp();
$this->quba->set_preferred_behaviour('deferredcbm');
- $this->slot = $this->quba->add_question($mc, 3);
+ $this->slot = $this->quba->add_question($mc, 1);
$this->quba->start_question_based_on($this->slot, $oldqa);
// Verify.
@@ -232,7 +232,7 @@ class qbehaviour_deferredcbm_walkthrough_test extends qbehaviour_walkthrough_tes
$this->get_contains_cbm_radio_expectation(2, true, true),
$this->get_does_not_contain_feedback_expectation(),
$this->get_does_not_contain_correctness_expectation());
- $this->assertEquals('A [' . question_cbm::get_string(question_cbm::HIGH) . ']',
+ $this->assertEquals('A [' . question_cbm::get_short_string(question_cbm::HIGH) . ']',
$this->quba->get_right_answer_summary($this->slot));
$this->assertRegExp('/' . preg_quote($mc->questiontext, '/') . '/',
$this->quba->get_question_summary($this->slot));
@@ -247,9 +247,9 @@ class qbehaviour_deferredcbm_walkthrough_test extends qbehaviour_walkthrough_tes
$this->check_current_mark(3);
$this->check_current_output(
$this->get_contains_mc_radio_expectation($rightindex, false, true),
- $this->get_contains_cbm_radio_expectation(3, false, true),
+ $this->get_contains_cbm_radio_expectation(question_cbm::HIGH, false, true),
$this->get_contains_correct_expectation());
- $this->assertRegExp('/(A) \[' . preg_quote(question_cbm::get_string(3), '/') . '\]/',
+ $this->assertRegExp('/(A) \[' . preg_quote(question_cbm::get_short_string(question_cbm::HIGH), '/') . '\]/',
$this->quba->get_response_summary($this->slot));
}
diff --git a/question/behaviour/deferredfeedback/behaviour.php b/question/behaviour/deferredfeedback/behaviour.php
index 5c5de7a31ee..3c67f9643f9 100644
--- a/question/behaviour/deferredfeedback/behaviour.php
+++ b/question/behaviour/deferredfeedback/behaviour.php
@@ -38,17 +38,10 @@ defined('MOODLE_INTERNAL') || die();
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qbehaviour_deferredfeedback extends question_behaviour_with_save {
- const IS_ARCHETYPAL = true;
-
public function is_compatible_question(question_definition $question) {
return $question instanceof question_automatically_gradable;
}
- public static function get_unused_display_options() {
- return array('correctness', 'marks', 'specificfeedback', 'generalfeedback',
- 'rightanswer');
- }
-
public function get_min_fraction() {
return $this->question->get_min_fraction();
}
diff --git a/question/behaviour/deferredfeedback/behaviourtype.php b/question/behaviour/deferredfeedback/behaviourtype.php
new file mode 100644
index 00000000000..b11e7f21aba
--- /dev/null
+++ b/question/behaviour/deferredfeedback/behaviourtype.php
@@ -0,0 +1,44 @@
+.
+
+/**
+ * Question behaviour type for deferred feedback behaviour.
+ *
+ * @package qbehaviour_deferredfeedback
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Question behaviour type information for deferred feedback behaviour.
+ *
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qbehaviour_deferredfeedback_type extends question_behaviour_type {
+ public function is_archetypal() {
+ return true;
+ }
+
+ public function get_unused_display_options() {
+ return array('correctness', 'marks', 'specificfeedback', 'generalfeedback',
+ 'rightanswer');
+ }
+}
diff --git a/question/behaviour/immediatecbm/behaviour.php b/question/behaviour/immediatecbm/behaviour.php
index 35f981f86a1..3e0a12256a4 100644
--- a/question/behaviour/immediatecbm/behaviour.php
+++ b/question/behaviour/immediatecbm/behaviour.php
@@ -34,21 +34,23 @@ require_once(dirname(__FILE__) . '/../immediatefeedback/behaviour.php');
* Question behaviour for immediate feedback with CBM.
*
* Each question has a submit button next to it along with some radio buttons
- * to input a certainly, that is, how sure they are that they are right.
+ * to input a certainty, that is, how sure they are that they are right.
* The student can submit their answer at any time for immediate feedback.
* Once the qustion is submitted, it is not possible for the student to change
- * their answer any more. The student's degree of certainly affects their score.
+ * their answer any more. The student's degree of certainty affects their score.
*
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qbehaviour_immediatecbm extends qbehaviour_immediatefeedback {
- const IS_ARCHETYPAL = true;
-
public function get_min_fraction() {
return question_cbm::adjust_fraction(parent::get_min_fraction(), question_cbm::HIGH);
}
+ public function get_max_fraction() {
+ return question_cbm::adjust_fraction(parent::get_max_fraction(), question_cbm::HIGH);
+ }
+
public function get_expected_data() {
if ($this->qa->get_state()->is_active()) {
return array(
@@ -148,8 +150,4 @@ class qbehaviour_immediatecbm extends qbehaviour_immediatefeedback {
}
return $summary;
}
-
- public static function adjust_random_guess_score($fraction) {
- return question_cbm::adjust_fraction($fraction, question_cbm::default_certainty());
- }
}
diff --git a/question/behaviour/immediatecbm/behaviourtype.php b/question/behaviour/immediatecbm/behaviourtype.php
new file mode 100644
index 00000000000..01a2ec336ff
--- /dev/null
+++ b/question/behaviour/immediatecbm/behaviourtype.php
@@ -0,0 +1,42 @@
+.
+
+/**
+ * Question behaviour type for immediate feedback with CBM behaviour.
+ *
+ * @package qbehaviour_adaptive
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(dirname(__FILE__) . '/../deferredcbm/behaviourtype.php');
+
+
+/**
+ * Question behaviour type information for immediate feedback with CBM.
+ *
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qbehaviour_immediatecbm_type extends qbehaviour_deferredcbm_type {
+
+ public function get_unused_display_options() {
+ return array();
+ }
+}
diff --git a/question/behaviour/immediatecbm/tests/walkthrough_test.php b/question/behaviour/immediatecbm/tests/walkthrough_test.php
index 66e58d6defc..794cae01cc6 100644
--- a/question/behaviour/immediatecbm/tests/walkthrough_test.php
+++ b/question/behaviour/immediatecbm/tests/walkthrough_test.php
@@ -58,7 +58,7 @@ class qbehaviour_immediatecbm_walkthrough_test extends qbehaviour_walkthrough_te
$this->get_contains_mc_radio_expectation(2, true, false),
$this->get_contains_submit_button_expectation(true),
$this->get_does_not_contain_feedback_expectation());
- $this->assertEquals('A [' . question_cbm::get_string(question_cbm::HIGH) . ']',
+ $this->assertEquals('A [' . question_cbm::get_short_string(question_cbm::HIGH) . ']',
$this->quba->get_right_answer_summary($this->slot));
$this->assertRegExp('/' . preg_quote($mc->questiontext, '/') . '/',
$this->quba->get_question_summary($this->slot));
@@ -84,13 +84,13 @@ class qbehaviour_immediatecbm_walkthrough_test extends qbehaviour_walkthrough_te
// Verify.
$this->check_current_state(question_state::$gradedright);
- $this->check_current_mark(2/3);
+ $this->check_current_mark(2);
$this->check_current_output(
$this->get_contains_mc_radio_expectation($rightindex, false, true),
$this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, false, false),
$this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, false, false),
$this->get_contains_correct_expectation());
- $this->assertEquals('A [' . question_cbm::get_string(2) . ']',
+ $this->assertEquals('A [' . question_cbm::get_short_string(2) . ']',
$this->quba->get_response_summary($this->slot));
$numsteps = $this->get_step_count();
@@ -101,7 +101,7 @@ class qbehaviour_immediatecbm_walkthrough_test extends qbehaviour_walkthrough_te
// Verify.
$this->assertEquals($numsteps, $this->get_step_count());
$this->check_current_state(question_state::$gradedright);
- $this->check_current_mark(2/3);
+ $this->check_current_mark(2);
$this->check_current_output(
$this->get_contains_mc_radio_expectation($rightindex, false, true),
$this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, false, false),
@@ -130,7 +130,7 @@ class qbehaviour_immediatecbm_walkthrough_test extends qbehaviour_walkthrough_te
$this->get_contains_partcorrect_expectation());
$autogradedstep = $this->get_step($this->get_step_count() - 2);
- $this->assertEquals($autogradedstep->get_fraction(), -10/9, '', 0.0000001);
+ $this->assertEquals($autogradedstep->get_fraction(), -2, '', 0.0000001);
}
public function test_immediatecbm_feedback_multichoice_try_to_submit_blank() {
@@ -215,7 +215,7 @@ class qbehaviour_immediatecbm_walkthrough_test extends qbehaviour_walkthrough_te
// Verify.
$this->check_current_state(question_state::$gradedright);
- $this->check_current_mark(1);
+ $this->check_current_mark(3);
$this->check_current_output(
$this->get_does_not_contain_validation_error_expectation());
}
@@ -258,7 +258,7 @@ class qbehaviour_immediatecbm_walkthrough_test extends qbehaviour_walkthrough_te
// Verify.
$this->check_current_state(question_state::$gradedwrong);
- $this->check_current_mark(-3);
+ $this->check_current_mark(-6);
$this->check_current_output(
$this->get_contains_mc_radio_expectation($wrongindex, false, true),
$this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, false, false),
@@ -270,7 +270,7 @@ class qbehaviour_immediatecbm_walkthrough_test extends qbehaviour_walkthrough_te
// Create a true-false question with correct answer true.
$tf = test_question_maker::make_question('truefalse', 'true');
- $this->start_attempt_at_question($tf, 'deferredcbm', 2);
+ $this->start_attempt_at_question($tf, 'immediatecbm', 2);
// Verify.
$this->check_current_state(question_state::$todo);
diff --git a/question/behaviour/immediatefeedback/behaviourtype.php b/question/behaviour/immediatefeedback/behaviourtype.php
new file mode 100644
index 00000000000..c366fe33126
--- /dev/null
+++ b/question/behaviour/immediatefeedback/behaviourtype.php
@@ -0,0 +1,39 @@
+.
+
+/**
+ * Question behaviour type for immediate feedback behaviour.
+ *
+ * @package qbehaviour_immediatefeedback
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Question behaviour type information for immediate feedback behaviour.
+ *
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qbehaviour_immediatefeedback_type extends question_behaviour_type {
+ public function is_archetypal() {
+ return true;
+ }
+}
diff --git a/question/behaviour/informationitem/behaviour.php b/question/behaviour/informationitem/behaviour.php
index 472a6e258c4..3a71d5fe5d5 100644
--- a/question/behaviour/informationitem/behaviour.php
+++ b/question/behaviour/informationitem/behaviour.php
@@ -15,7 +15,7 @@
// along with Moodle. If not, see .
/**
- * This behaviour is for informaiton items.
+ * This behaviour is for information items.
*
* @package qbehaviour
* @subpackage informationitem
diff --git a/question/behaviour/informationitem/behaviourtype.php b/question/behaviour/informationitem/behaviourtype.php
new file mode 100644
index 00000000000..965faa10775
--- /dev/null
+++ b/question/behaviour/informationitem/behaviourtype.php
@@ -0,0 +1,36 @@
+.
+
+/**
+ * Question behaviour type for information item behaviour.
+ *
+ * @package qbehaviour_informationitem
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Question behaviour type information for informationitem items.
+ *
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qbehaviour_informationitem_type extends question_behaviour_type {
+}
diff --git a/question/behaviour/interactive/behaviour.php b/question/behaviour/interactive/behaviour.php
index 27e0b303ba4..29f72b74b04 100644
--- a/question/behaviour/interactive/behaviour.php
+++ b/question/behaviour/interactive/behaviour.php
@@ -40,8 +40,6 @@ defined('MOODLE_INTERNAL') || die();
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qbehaviour_interactive extends question_behaviour_with_save {
- const IS_ARCHETYPAL = true;
-
/**
* Special value used for {@link question_display_options::$readonly when
* we are showing the try again button to the student during an attempt.
diff --git a/question/behaviour/interactive/behaviourtype.php b/question/behaviour/interactive/behaviourtype.php
new file mode 100644
index 00000000000..30713079102
--- /dev/null
+++ b/question/behaviour/interactive/behaviourtype.php
@@ -0,0 +1,39 @@
+.
+
+/**
+ * Question behaviour type for interactive behaviour.
+ *
+ * @package qbehaviour_interactive
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Question behaviour type information for interactive behaviour.
+ *
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qbehaviour_interactive_type extends question_behaviour_type {
+ public function is_archetypal() {
+ return true;
+ }
+}
diff --git a/question/behaviour/interactivecountback/behaviour.php b/question/behaviour/interactivecountback/behaviour.php
index 8a14cb76818..20c650b6187 100644
--- a/question/behaviour/interactivecountback/behaviour.php
+++ b/question/behaviour/interactivecountback/behaviour.php
@@ -62,7 +62,6 @@ require_once(dirname(__FILE__) . '/../interactive/behaviour.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qbehaviour_interactivecountback extends qbehaviour_interactive {
- const IS_ARCHETYPAL = false;
public function is_compatible_question(question_definition $question) {
return $question instanceof question_automatically_gradable_with_countback;
diff --git a/question/behaviour/interactivecountback/behaviourtype.php b/question/behaviour/interactivecountback/behaviourtype.php
new file mode 100644
index 00000000000..abbf0a1aba1
--- /dev/null
+++ b/question/behaviour/interactivecountback/behaviourtype.php
@@ -0,0 +1,41 @@
+.
+
+/**
+ * Question behaviour type for interactive behaviour with count-back scoring behaviour.
+ *
+ * @package qbehaviour_interactivecountback
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(dirname(__FILE__) . '/../interactive/behaviourtype.php');
+
+
+/**
+ * Question behaviour type information for interactive behaviour with count-back scoring.
+ *
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qbehaviour_interactivecountback_type extends qbehaviour_interactive_type {
+ public function is_archetypal() {
+ return false;
+ }
+}
diff --git a/question/behaviour/manualgraded/behaviour.php b/question/behaviour/manualgraded/behaviour.php
index 78e9e54b62a..e37540d6fa6 100644
--- a/question/behaviour/manualgraded/behaviour.php
+++ b/question/behaviour/manualgraded/behaviour.php
@@ -38,17 +38,11 @@ defined('MOODLE_INTERNAL') || die();
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qbehaviour_manualgraded extends question_behaviour_with_save {
- const IS_ARCHETYPAL = true;
public function is_compatible_question(question_definition $question) {
return $question instanceof question_with_responses;
}
- public static function get_unused_display_options() {
- return array('correctness', 'marks', 'specificfeedback', 'generalfeedback',
- 'rightanswer');
- }
-
public function adjust_display_options(question_display_options $options) {
parent::adjust_display_options($options);
diff --git a/question/behaviour/manualgraded/behaviourtype.php b/question/behaviour/manualgraded/behaviourtype.php
new file mode 100644
index 00000000000..b609cb1b403
--- /dev/null
+++ b/question/behaviour/manualgraded/behaviourtype.php
@@ -0,0 +1,44 @@
+.
+
+/**
+ * Question behaviour type for manually graded behaviour.
+ *
+ * @package qbehaviour_manualgraded
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Question behaviour type information for manually graded behaviour.
+ *
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qbehaviour_manualgraded_type extends question_behaviour_type {
+ public function is_archetypal() {
+ return true;
+ }
+
+ public function get_unused_display_options() {
+ return array('correctness', 'marks', 'specificfeedback', 'generalfeedback',
+ 'rightanswer');
+ }
+}
diff --git a/question/behaviour/missing/behaviour.php b/question/behaviour/missing/behaviour.php
index 3475040a767..a1e607c1f76 100644
--- a/question/behaviour/missing/behaviour.php
+++ b/question/behaviour/missing/behaviour.php
@@ -15,7 +15,7 @@
// along with Moodle. If not, see .
/**
- * Fake question behaviour that is used when the actual qim was not
+ * Fake question behaviour that is used when the actual behaviour was not
* available.
*
* @package qbehaviour
@@ -33,7 +33,7 @@ defined('MOODLE_INTERNAL') || die();
* is not available.
*
* Imagine, for example, that a quiz attempt has been restored from another
- * Moodle site with more behaviours installed, or an behaviour
+ * Moodle site with more behaviours installed, or a behaviour
* that used to be available in this site has been uninstalled. Obviously all we
* can do is have some code to prevent fatal errors.
*
@@ -68,4 +68,9 @@ class qbehaviour_missing extends question_behaviour {
throw new coding_exception('The behaviour used for this question is not available. ' .
'No processing is possible.');
}
+
+ public function get_max_fraction() {
+ throw new coding_exception('The behaviour used for this question is not available. ' .
+ 'No processing is possible.');
+ }
}
diff --git a/question/behaviour/missing/behaviourtype.php b/question/behaviour/missing/behaviourtype.php
new file mode 100644
index 00000000000..c421ed03d16
--- /dev/null
+++ b/question/behaviour/missing/behaviourtype.php
@@ -0,0 +1,38 @@
+.
+
+/**
+ * Fake question behaviour type that is used when the actual behaviour is not
+ * available.
+ *
+ * @package qbehaviour_missing
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Fake question behaviour type information that is used when the actual
+ * behaviour is not available.
+ *
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qbehaviour_missing_type extends question_behaviour_type {
+}
diff --git a/question/behaviour/missing/tests/missingbehaviour_test.php b/question/behaviour/missing/tests/missingbehaviour_test.php
index 4922ae7b458..7c02042850b 100644
--- a/question/behaviour/missing/tests/missingbehaviour_test.php
+++ b/question/behaviour/missing/tests/missingbehaviour_test.php
@@ -53,25 +53,32 @@ class qbehaviour_missing_test extends advanced_testcase {
$behaviour->process_action(new question_attempt_pending_step(array()));
}
- public function test_missing_cannot_get_min_grade() {
+ public function test_missing_cannot_get_min_fraction() {
$qa = new question_attempt(test_question_maker::make_question('truefalse', 'true'), 0);
$behaviour = new qbehaviour_missing($qa, 'deferredfeedback');
$this->setExpectedException('moodle_exception');
$behaviour->get_min_fraction();
}
+ public function test_missing_cannot_get_max_fraction() {
+ $qa = new question_attempt(test_question_maker::make_question('truefalse', 'true'), 0);
+ $behaviour = new qbehaviour_missing($qa, 'deferredfeedback');
+ $this->setExpectedException('moodle_exception');
+ $behaviour->get_max_fraction();
+ }
+
public function test_render_missing() {
$records = new question_test_recordset(array(
array('questionattemptid', 'contextid', 'questionusageid', 'slot',
- 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'flagged',
+ 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'maxfraction', 'flagged',
'questionsummary', 'rightanswer', 'responsesummary',
'timemodified', 'attemptstepid', 'sequencenumber', 'state', 'fraction',
'timecreated', 'userid', 'name', 'value'),
- array(1, 123, 1, 1, 'strangeunknown', -1, 1, 2.0000000, 0.0000000, 0, '', '', '',
+ array(1, 123, 1, 1, 'strangeunknown', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '',
1256233790, 1, 0, 'todo', null, 1256233700, 1, '_order', '1,2,3'),
- array(1, 123, 1, 1, 'strangeunknown', -1, 1, 2.0000000, 0.0000000, 0, '', '', '',
+ array(1, 123, 1, 1, 'strangeunknown', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '',
1256233790, 2, 1, 'complete', 0.50, 1256233705, 1, '-submit', '1'),
- array(1, 123, 1, 1, 'strangeunknown', -1, 1, 2.0000000, 0.0000000, 0, '', '', '',
+ array(1, 123, 1, 1, 'strangeunknown', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '',
1256233790, 2, 1, 'complete', 0.50, 1256233705, 1, 'choice0', '1'),
));
diff --git a/question/behaviour/rendererbase.php b/question/behaviour/rendererbase.php
index 6a24e38d759..203c5b35fec 100644
--- a/question/behaviour/rendererbase.php
+++ b/question/behaviour/rendererbase.php
@@ -140,11 +140,15 @@ abstract class qbehaviour_renderer extends plugin_renderer_base {
'type' => 'hidden',
'name' => $qa->get_control_field_name('minfraction'),
'value' => $qa->get_min_fraction(),
+ )) . html_writer::empty_tag('input', array(
+ 'type' => 'hidden',
+ 'name' => $qa->get_control_field_name('maxfraction'),
+ 'value' => $qa->get_max_fraction(),
));
$errorclass = '';
$error = '';
- if ($currentmark > $maxmark || $currentmark < $maxmark * $qa->get_min_fraction()) {
+ if ($currentmark > $maxmark * $qa->get_max_fraction() || $currentmark < $maxmark * $qa->get_min_fraction()) {
$errorclass = ' error';
$error = html_writer::tag('span', get_string('manualgradeoutofrange', 'question'),
array('class' => 'error')) . html_writer::empty_tag('br');
@@ -231,4 +235,40 @@ abstract class qbehaviour_renderer extends plugin_renderer_base {
public function head_code(question_attempt $qa) {
return '';
}
+
+ /**
+ * Generate the display of the marks for this question.
+ * @param question_attempt $qa the question attempt to display.
+ * @param core_question_renderer $qoutput the renderer for standard parts of questions.
+ * @param question_display_options $options controls what should and should not be displayed.
+ * @return HTML fragment.
+ */
+ public function mark_summary(question_attempt $qa, core_question_renderer $qoutput,
+ question_display_options $options) {
+ return $qoutput->standard_mark_summary($qa, $this, $options);
+ }
+
+ /**
+ * Generate the display of the available marks for this question.
+ * @param question_attempt $qa the question attempt to display.
+ * @param core_question_renderer $qoutput the renderer for standard parts of questions.
+ * @param question_display_options $options controls what should and should not be displayed.
+ * @return HTML fragment.
+ */
+ public function marked_out_of_max(question_attempt $qa, core_question_renderer $qoutput,
+ question_display_options $options) {
+ return $qoutput->standard_marked_out_of_max($qa, $options);
+ }
+
+ /**
+ * Generate the display of the marks for this question out of the available marks.
+ * @param question_attempt $qa the question attempt to display.
+ * @param core_question_renderer $qoutput the renderer for standard parts of questions.
+ * @param question_display_options $options controls what should and should not be displayed.
+ * @return HTML fragment.
+ */
+ public function mark_out_of_max(question_attempt $qa, core_question_renderer $qoutput,
+ question_display_options $options) {
+ return $qoutput->standard_mark_out_of_max($qa, $options);
+ }
}
diff --git a/question/behaviour/upgrade.txt b/question/behaviour/upgrade.txt
index 5bd432d274e..51d17c080ed 100644
--- a/question/behaviour/upgrade.txt
+++ b/question/behaviour/upgrade.txt
@@ -4,23 +4,49 @@ This files describes API changes for question behaviour plugins.
1) Legacy required_question_definition_type no longer supported. (See 2.2 point 2) below.)
+2) Behaviours now have to define an extra class
+class qbehaviour_mybehaviour_type extends question_behaviour_type {
+ This class returns information about the type of behaviour, as opposed to
+ the qbehaviour_mybehaviour class which controls a particular
+ question_attempt. That is like the difference between the qtype_mytype and
+ the qtype_mytype_question classes.
+
+ Practically, what this means is that any of the methods that used to be
+ static methods of qbehaviour_mybehaviour class are now normal instance
+ methods of the qbehaviour_mybehaviour_type class. Specifically.
+ 2.5 / qbehaviour_mybehaviour -> 2.6 / qbehaviour_mybehaviour_type
+ IS_ARCHETYPAL -> is_archetypal()
+ adjust_random_guess_score() -> adjust_random_guess_score()
+ get_unused_display_options() -> get_unused_display_options()
+
+3) The static method is_manual_grade_in_range has moved from the
+ question_behaviour class to the question_engine class.
+
+4) Behaviours can now control how the marks information is displayed in the
+ grey info area to the left of the question. There is a new method
+ mark_summary that you can override, although the default implementation is
+ fine in most cases. it uses the marked_out_of_max and mark_out_of_max methods
+ as appropriate, so you may just wish to override those.
+
+
=== 2.3 ===
1) This plugin type now supports cron in the standard way. If required, Create a
lib.php file containing
function qbehaviour_mypluginname_cron() {};
+
=== 2.2 ===
1) The old
public static function get_required_behaviours()
-method is no more. Instead use the ->dependencies facility in version.php. E.g.
+ method is no more. Instead use the ->dependencies facility in version.php. E.g.
$plugin->dependencies = array(
'qbehaviour_immediatefeedback' => 2011102700,
'qbehaviour_deferredcbm' => 2011102700
);
2) The old required_question_definition_type method has been replaced by a new
-is_compatible_question method. You should change your behaviour to override the
-new method, not the old one. This change has been implemented in a
-backwards-compatible way, so behaviours will not break.
+ is_compatible_question method. You should change your behaviour to override
+ the new method, not the old one. This change has been implemented in a
+ backwards-compatible way, so behaviours will not break.
diff --git a/question/engine/datalib.php b/question/engine/datalib.php
index 68e3bc4e634..41d1ce1e317 100644
--- a/question/engine/datalib.php
+++ b/question/engine/datalib.php
@@ -106,6 +106,7 @@ class question_engine_data_mapper {
$record->variant = $qa->get_variant();
$record->maxmark = $qa->get_max_mark();
$record->minfraction = $qa->get_min_fraction();
+ $record->maxfraction = $qa->get_max_fraction();
$record->flagged = $qa->is_flagged();
$record->questionsummary = $qa->get_question_summary();
if (core_text::strlen($record->questionsummary) > question_bank::MAX_SUMMARY_LENGTH) {
@@ -259,6 +260,7 @@ SELECT
qa.variant,
qa.maxmark,
qa.minfraction,
+ qa.maxfraction,
qa.flagged,
qa.questionsummary,
qa.rightanswer,
@@ -318,6 +320,7 @@ SELECT
qa.variant,
qa.maxmark,
qa.minfraction,
+ qa.maxfraction,
qa.flagged,
qa.questionsummary,
qa.rightanswer,
@@ -377,6 +380,7 @@ ORDER BY
qa.variant,
qa.maxmark,
qa.minfraction,
+ qa.maxfraction,
qa.flagged,
qa.questionsummary,
qa.rightanswer,
@@ -641,6 +645,7 @@ SELECT
qa.variant,
qa.maxmark,
qa.minfraction,
+ qa.maxfraction,
qa.flagged,
qa.questionsummary,
qa.rightanswer,
@@ -708,6 +713,7 @@ ORDER BY
$record->id = $qa->get_database_id();
$record->maxmark = $qa->get_max_mark();
$record->minfraction = $qa->get_min_fraction();
+ $record->maxfraction = $qa->get_max_fraction();
$record->flagged = $qa->is_flagged();
$record->questionsummary = $qa->get_question_summary();
$record->rightanswer = $qa->get_right_answer_summary();
@@ -949,6 +955,7 @@ ORDER BY
{$alias}qa.variant,
{$alias}qa.maxmark,
{$alias}qa.minfraction,
+ {$alias}qa.maxfraction,
{$alias}qa.flagged,
{$alias}qa.questionsummary,
{$alias}qa.rightanswer,
diff --git a/question/engine/lib.php b/question/engine/lib.php
index 510be36ee2b..88c0065d418 100644
--- a/question/engine/lib.php
+++ b/question/engine/lib.php
@@ -37,6 +37,7 @@ require_once(dirname(__FILE__) . '/bank.php');
require_once(dirname(__FILE__) . '/../type/questiontypebase.php');
require_once(dirname(__FILE__) . '/../type/questionbase.php');
require_once(dirname(__FILE__) . '/../type/rendererbase.php');
+require_once(dirname(__FILE__) . '/../behaviour/behaviourtypebase.php');
require_once(dirname(__FILE__) . '/../behaviour/behaviourbase.php');
require_once(dirname(__FILE__) . '/../behaviour/rendererbase.php');
require_once($CFG->libdir . '/questionlib.php');
@@ -56,6 +57,9 @@ abstract class question_engine {
/** @var array behaviour name => 1. Records which behaviours have been loaded. */
private static $loadedbehaviours = array();
+ /** @var array behaviour name => question_behaviour_type for this behaviour. */
+ private static $behaviourtypes = array();
+
/**
* Create a new {@link question_usage_by_activity}. The usage is
* created in memory. If you want it to persist, you will need to call
@@ -127,6 +131,21 @@ abstract class question_engine {
$dm->set_max_mark_in_attempts($qubaids, $slot, $newmaxmark);
}
+ /**
+ * Validate that the manual grade submitted for a particular question is in range.
+ * @param int $qubaid the question_usage id.
+ * @param int $slot the slot number within the usage.
+ * @return bool whether the submitted data is in range.
+ */
+ public static function is_manual_grade_in_range($qubaid, $slot) {
+ $prefix = 'q' . $qubaid . ':' . $slot . '_';
+ $mark = question_utils::optional_param_mark($prefix . '-mark');
+ $maxmark = optional_param($prefix . '-maxmark', null, PARAM_FLOAT);
+ $minfraction = optional_param($prefix . ':minfraction', null, PARAM_FLOAT);
+ $maxfraction = optional_param($prefix . ':maxfraction', null, PARAM_FLOAT);
+ return is_null($mark) || ($mark >= $minfraction * $maxmark && $mark <= $maxfraction * $maxmark);
+ }
+
/**
* @param array $questionids of question ids.
* @param qubaid_condition $qubaids ids of the usages to consider.
@@ -150,12 +169,13 @@ abstract class question_engine {
* @return question_behaviour an instance of appropriate behaviour class.
*/
public static function make_archetypal_behaviour($preferredbehaviour, question_attempt $qa) {
- self::load_behaviour_class($preferredbehaviour);
- $class = 'qbehaviour_' . $preferredbehaviour;
- if (!constant($class . '::IS_ARCHETYPAL')) {
+ if (!self::is_behaviour_archetypal($preferredbehaviour)) {
throw new coding_exception('The requested behaviour is not actually ' .
'an archetypal one.');
}
+
+ self::load_behaviour_class($preferredbehaviour);
+ $class = 'qbehaviour_' . $preferredbehaviour;
return new $class($qa, $preferredbehaviour);
}
@@ -165,16 +185,11 @@ abstract class question_engine {
* not relevant to this behaviour before a 'finish' action.
*/
public static function get_behaviour_unused_display_options($behaviour) {
- self::load_behaviour_class($behaviour);
- $class = 'qbehaviour_' . $behaviour;
- if (!method_exists($class, 'get_unused_display_options')) {
- return question_behaviour::get_unused_display_options();
- }
- return call_user_func(array($class, 'get_unused_display_options'));
+ return self::get_behaviour_type($behaviour)->get_unused_display_options();
}
/**
- * Create an behaviour for a particular type. If that type cannot be
+ * Create a behaviour for a particular type. If that type cannot be
* found, return an instance of qbehaviour_missing.
*
* Normally you should use {@link make_archetypal_behaviour()}, or
@@ -213,9 +228,67 @@ abstract class question_engine {
throw new coding_exception('Unknown question behaviour ' . $behaviour);
}
include_once($file);
+
+ $class = 'qbehaviour_' . $behaviour;
+ if (!class_exists($class)) {
+ throw new coding_exception('Question behaviour ' . $behaviour .
+ ' does not define the required class ' . $class . '.');
+ }
+
self::$loadedbehaviours[$behaviour] = 1;
}
+ /**
+ * Create a behaviour for a particular type. If that type cannot be
+ * found, return an instance of qbehaviour_missing.
+ *
+ * Normally you should use {@link make_archetypal_behaviour()}, or
+ * call the constructor of a particular model class directly. This method
+ * is only intended for use by {@link question_attempt::load_from_records()}.
+ *
+ * @param string $behaviour the type of model to create.
+ * @param question_attempt $qa the question attempt the model will process.
+ * @param string $preferredbehaviour the preferred behaviour for the containing usage.
+ * @return question_behaviour_type an instance of appropriate behaviour class.
+ */
+ public static function get_behaviour_type($behaviour) {
+
+ if (array_key_exists($behaviour, self::$behaviourtypes)) {
+ return self::$behaviourtypes[$behaviour];
+ }
+
+ self::load_behaviour_type_class($behaviour);
+
+ $class = 'qbehaviour_' . $behaviour . '_type';
+ if (class_exists($class)) {
+ self::$behaviourtypes[$behaviour] = new $class();
+ } else {
+ debugging('Question behaviour ' . $behaviour .
+ ' does not define the required class ' . $class . '.', DEBUG_DEVELOPER);
+ self::$behaviourtypes[$behaviour] = new question_behaviour_type_fallback($behaviour);
+ }
+
+ return self::$behaviourtypes[$behaviour];
+ }
+
+ /**
+ * Load the behaviour type class for a particular behaviour. That is,
+ * include_once('/question/behaviour/' . $behaviour . '/behaviourtype.php').
+ * @param string $behaviour the behaviour name. For example 'interactive' or 'deferredfeedback'.
+ */
+ protected static function load_behaviour_type_class($behaviour) {
+ global $CFG;
+ if (isset(self::$behaviourtypes[$behaviour])) {
+ return;
+ }
+ $file = $CFG->dirroot . '/question/behaviour/' . $behaviour . '/behaviourtype.php';
+ if (!is_readable($file)) {
+ debugging('Question behaviour ' . $behaviour .
+ ' is missing the behaviourtype.php file.', DEBUG_DEVELOPER);
+ }
+ include_once($file);
+ }
+
/**
* Return an array where the keys are the internal names of the archetypal
* behaviours, and the values are a human-readable name. An
@@ -241,9 +314,7 @@ abstract class question_engine {
* @return bool whether this is an archetypal behaviour.
*/
public static function is_behaviour_archetypal($behaviour) {
- self::load_behaviour_class($behaviour);
- $plugin = 'qbehaviour_' . $behaviour;
- return constant($plugin . '::IS_ARCHETYPAL');
+ return self::get_behaviour_type($behaviour)->is_archetypal();
}
/**
@@ -323,7 +394,7 @@ abstract class question_engine {
}
/**
- * Get the translated name of an behaviour, for display in the UI.
+ * Get the translated name of a behaviour, for display in the UI.
* @param string $behaviour the internal name of the model.
* @return string name from the current language pack.
*/
diff --git a/question/engine/questionattempt.php b/question/engine/questionattempt.php
index 1f36349fadd..3c3acffb58d 100644
--- a/question/engine/questionattempt.php
+++ b/question/engine/questionattempt.php
@@ -87,15 +87,25 @@ class question_attempt {
/** @var int which variant of the question to use. */
protected $variant;
- /** @var number the maximum mark that can be scored at this question. */
+ /**
+ * @var float the maximum mark that can be scored at this question.
+ * Actually, this is only really a nominal maximum. It might be better thought
+ * of as the question weight.
+ */
protected $maxmark;
/**
- * @var number the minimum fraction that can be scored at this question, so
+ * @var float the minimum fraction that can be scored at this question, so
* the minimum mark is $this->minfraction * $this->maxmark.
*/
protected $minfraction = null;
+ /**
+ * @var float the maximum fraction that can be scored at this question, so
+ * the maximum mark is $this->maxfraction * $this->maxmark.
+ */
+ protected $maxfraction = null;
+
/**
* @var string plain text summary of the variant of the question the
* student saw. Intended for reporting purposes.
@@ -649,19 +659,32 @@ class question_attempt {
return $fraction * $this->maxmark;
}
- /** @return number the maximum mark possible for this question attempt. */
+ /**
+ * @return float the maximum mark possible for this question attempt.
+ * In fact, this is not strictly the maximum, becuase get_max_fraction may
+ * return a number greater than 1. It might be better to think of this as a
+ * question weight.
+ */
public function get_max_mark() {
return $this->maxmark;
}
- /** @return number the maximum mark possible for this question attempt. */
+ /** @return float the maximum mark possible for this question attempt. */
public function get_min_fraction() {
if (is_null($this->minfraction)) {
- throw new coding_exception('This question_attempt has not been started yet, the min fraction is not yet konwn.');
+ throw new coding_exception('This question_attempt has not been started yet, the min fraction is not yet known.');
}
return $this->minfraction;
}
+ /** @return float the maximum mark possible for this question attempt. */
+ public function get_max_fraction() {
+ if (is_null($this->maxfraction)) {
+ throw new coding_exception('This question_attempt has not been started yet, the max fraction is not yet known.');
+ }
+ return $this->maxfraction;
+ }
+
/**
* The current mark, formatted to the stated number of decimal places. Uses
* {@link format_float()} to format floats according to the current locale.
@@ -885,8 +908,9 @@ class question_attempt {
$this->behaviour = new $class($this, $preferredbehaviour);
}
- // Record the minimum fraction.
+ // Record the minimum and maximum fractions.
$this->minfraction = $this->behaviour->get_min_fraction();
+ $this->maxfraction = $this->behaviour->get_max_fraction();
// Initialise the first step.
$firststep = new question_attempt_step($submitteddata, $timestamp, $userid, $existingstepid);
@@ -1287,6 +1311,7 @@ class question_attempt {
$qa->set_slot($record->slot);
$qa->variant = $record->variant + 0;
$qa->minfraction = $record->minfraction + 0;
+ $qa->maxfraction = $record->maxfraction + 0;
$qa->set_flagged($record->flagged);
$qa->questionsummary = $record->questionsummary;
$qa->rightanswer = $record->rightanswer;
@@ -1389,6 +1414,7 @@ class question_attempt_with_restricted_history extends question_attempt {
$this->question = $this->baseqa->question;
$this->maxmark = $this->baseqa->maxmark;
$this->minfraction = $this->baseqa->minfraction;
+ $this->maxfraction = $this->baseqa->maxfraction;
$this->questionsummary = $this->baseqa->questionsummary;
$this->responsesummary = $this->baseqa->responsesummary;
$this->rightanswer = $this->baseqa->rightanswer;
diff --git a/question/engine/questionattemptstep.php b/question/engine/questionattemptstep.php
index 892c738d588..7f7e238af68 100644
--- a/question/engine/questionattemptstep.php
+++ b/question/engine/questionattemptstep.php
@@ -32,7 +32,7 @@ defined('MOODLE_INTERNAL') || die();
*
* The most important attributes of a step are the state, which is one of the
* {@link question_state} constants, the fraction, which may be null, or a
- * number bewteen the attempt's minfraction and 1.0, and the array of submitted
+ * number bewteen the attempt's minfraction and maxfraction, and the array of submitted
* data, about which more later.
*
* A step also tracks the time it was created, and the user responsible for
@@ -76,7 +76,10 @@ class question_attempt_step {
*/
private $state;
- /** @var null|number the fraction (grade on a scale of minfraction .. 1.0) or null. */
+ /**
+ * @var null|number the fraction (grade on a scale of
+ * minfraction .. maxfraction, normally 0..1) or null.
+ */
private $fraction = null;
/** @var integer the timestamp when this step was created. */
@@ -148,7 +151,8 @@ class question_attempt_step {
}
/**
- * @return null|number the fraction (grade on a scale of minfraction .. 1.0)
+ * @return null|number the fraction (grade on a scale of
+ * minfraction .. maxfraction, normally 0..1),
* or null if this step has not been marked.
*/
public function get_fraction() {
@@ -291,7 +295,7 @@ class question_attempt_step {
}
/**
- * @param string $name the name of an behaviour variable to look for in the submitted data.
+ * @param string $name the name of a behaviour variable to look for in the submitted data.
* @return bool whether a variable with this name exists in the question type data.
*/
public function has_behaviour_var($name) {
@@ -299,7 +303,7 @@ class question_attempt_step {
}
/**
- * @param string $name the name of an behaviour variable to look for in the submitted data.
+ * @param string $name the name of a behaviour variable to look for in the submitted data.
* @return string the requested variable, or null if the variable is not set.
*/
public function get_behaviour_var($name) {
diff --git a/question/engine/questionusage.php b/question/engine/questionusage.php
index be165b488d8..b4a1a29527e 100644
--- a/question/engine/questionusage.php
+++ b/question/engine/questionusage.php
@@ -326,6 +326,27 @@ class question_usage_by_activity {
return $mark;
}
+ /**
+ * Get summary information about this usage.
+ *
+ * Some behaviours may be able to provide interesting summary information
+ * about the attempt as a whole, and this method provides access to that data.
+ * To see how this works, try setting a quiz to one of the CBM behaviours,
+ * and then look at the extra information displayed at the top of the quiz
+ * review page once you have sumitted an attempt.
+ *
+ * In the return value, the array keys are identifiers of the form
+ * qbehaviour_behaviourname_meaningfullkey. For qbehaviour_deferredcbm_highsummary.
+ * The values are arrays with two items, title and content. Each of these
+ * will be either a string, or a renderable.
+ *
+ * @return array as described above.
+ */
+ public function get_summary_information(question_display_options $options) {
+ return question_engine::get_behaviour_type($this->preferredbehaviour)
+ ->summarise_usage($this, $options);
+ }
+
/**
* @return string a simple textual summary of the question that was asked.
*/
diff --git a/question/engine/renderer.php b/question/engine/renderer.php
index f6793d4113d..b07317f7dea 100644
--- a/question/engine/renderer.php
+++ b/question/engine/renderer.php
@@ -115,7 +115,7 @@ class core_question_renderer extends plugin_renderer_base {
$output = '';
$output .= $this->number($number);
$output .= $this->status($qa, $behaviouroutput, $options);
- $output .= $this->mark_summary($qa, $options);
+ $output .= $this->mark_summary($qa, $behaviouroutput, $options);
$output .= $this->question_flag($qa, $options->flags);
$output .= $this->edit_question_link($qa, $options);
return $output;
@@ -173,30 +173,59 @@ class core_question_renderer extends plugin_renderer_base {
/**
* Generate the display of the marks for this question.
* @param question_attempt $qa the question attempt to display.
+ * @param qbehaviour_renderer $behaviouroutput the behaviour renderer, which can generate a custom display.
* @param question_display_options $options controls what should and should not be displayed.
* @return HTML fragment.
*/
- protected function mark_summary(question_attempt $qa, question_display_options $options) {
+ protected function mark_summary(question_attempt $qa, qbehaviour_renderer $behaviouroutput, question_display_options $options) {
+ return html_writer::nonempty_tag('div',
+ $behaviouroutput->mark_summary($qa, $this, $options),
+ array('class' => 'grade'));
+ }
+
+ /**
+ * Generate the display of the marks for this question.
+ * @param question_attempt $qa the question attempt to display.
+ * @param question_display_options $options controls what should and should not be displayed.
+ * @return HTML fragment.
+ */
+ public function standard_mark_summary(question_attempt $qa, qbehaviour_renderer $behaviouroutput, question_display_options $options) {
if (!$options->marks) {
return '';
- }
- if ($qa->get_max_mark() == 0) {
- $summary = get_string('notgraded', 'question');
+ } else if ($qa->get_max_mark() == 0) {
+ return get_string('notgraded', 'question');
} else if ($options->marks == question_display_options::MAX_ONLY ||
is_null($qa->get_fraction())) {
- $summary = get_string('markedoutofmax', 'question',
- $qa->format_max_mark($options->markdp));
+ return $behaviouroutput->marked_out_of_max($qa, $this, $options);
} else {
- $a = new stdClass();
- $a->mark = $qa->format_mark($options->markdp);
- $a->max = $qa->format_max_mark($options->markdp);
- $summary = get_string('markoutofmax', 'question', $a);
+ return $behaviouroutput->mark_out_of_max($qa, $this, $options);
}
+ }
- return html_writer::tag('div', $summary, array('class' => 'grade'));
+ /**
+ * Generate the display of the available marks for this question.
+ * @param question_attempt $qa the question attempt to display.
+ * @param question_display_options $options controls what should and should not be displayed.
+ * @return HTML fragment.
+ */
+ public function standard_marked_out_of_max(question_attempt $qa, question_display_options $options) {
+ return get_string('markedoutofmax', 'question', $qa->format_max_mark($options->markdp));
+ }
+
+ /**
+ * Generate the display of the marks for this question out of the available marks.
+ * @param question_attempt $qa the question attempt to display.
+ * @param question_display_options $options controls what should and should not be displayed.
+ * @return HTML fragment.
+ */
+ public function standard_mark_out_of_max(question_attempt $qa, question_display_options $options) {
+ $a = new stdClass();
+ $a->mark = $qa->format_mark($options->markdp);
+ $a->max = $qa->format_max_mark($options->markdp);
+ return get_string('markoutofmax', 'question', $a);
}
/**
diff --git a/question/engine/tests/helpers.php b/question/engine/tests/helpers.php
index 5542014036e..a02cd0c7315 100644
--- a/question/engine/tests/helpers.php
+++ b/question/engine/tests/helpers.php
@@ -43,6 +43,9 @@ class testable_question_attempt extends question_attempt {
public function set_min_fraction($fraction) {
$this->minfraction = $fraction;
}
+ public function set_max_fraction($fraction) {
+ $this->maxfraction = $fraction;
+ }
public function set_behaviour(question_behaviour $behaviour) {
$this->behaviour = $behaviour;
}
diff --git a/question/engine/tests/questionattempt_db_test.php b/question/engine/tests/questionattempt_db_test.php
index 1f9f9701f35..bef878838b5 100644
--- a/question/engine/tests/questionattempt_db_test.php
+++ b/question/engine/tests/questionattempt_db_test.php
@@ -44,18 +44,18 @@ class question_attempt_db_test extends data_loading_method_test_base {
public function test_load() {
$records = new question_test_recordset(array(
array('questionattemptid', 'contextid', 'questionusageid', 'slot',
- 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'flagged',
+ 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'maxfraction', 'flagged',
'questionsummary', 'rightanswer', 'responsesummary', 'timemodified',
'attemptstepid', 'sequencenumber', 'state', 'fraction',
'timecreated', 'userid', 'name', 'value'),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo', null, 1256233700, 1, null, null),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 2, 1, 'complete', null, 1256233705, 1, 'answer', '1'),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1, '', '', '', 1256233790, 3, 2, 'complete', null, 1256233710, 1, 'answer', '0'),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 4, 3, 'complete', null, 1256233715, 1, 'answer', '1'),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 5, 4, 'gradedright', 1.0000000, 1256233720, 1, '-finish', '1'),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 6, 5, 'mangrpartial', 0.5000000, 1256233790, 1, '-comment', 'Not good enough!'),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 6, 5, 'mangrpartial', 0.5000000, 1256233790, 1, '-mark', '1'),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 6, 5, 'mangrpartial', 0.5000000, 1256233790, 1, '-maxmark', '2'),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo', null, 1256233700, 1, null, null),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 2, 1, 'complete', null, 1256233705, 1, 'answer', '1'),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 1, '', '', '', 1256233790, 3, 2, 'complete', null, 1256233710, 1, 'answer', '0'),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 4, 3, 'complete', null, 1256233715, 1, 'answer', '1'),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 5, 4, 'gradedright', 1.0000000, 1256233720, 1, '-finish', '1'),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 6, 5, 'mangrpartial', 0.5000000, 1256233790, 1, '-comment', 'Not good enough!'),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 6, 5, 'mangrpartial', 0.5000000, 1256233790, 1, '-mark', '1'),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 6, 5, 'mangrpartial', 0.5000000, 1256233790, 1, '-maxmark', '2'),
));
$question = test_question_maker::make_question('truefalse', 'true');
@@ -117,11 +117,11 @@ class question_attempt_db_test extends data_loading_method_test_base {
public function test_load_missing_question() {
$records = new question_test_recordset(array(
array('questionattemptid', 'contextid', 'questionusageid', 'slot',
- 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'flagged',
+ 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'maxfraction', 'flagged',
'questionsummary', 'rightanswer', 'responsesummary', 'timemodified',
'attemptstepid', 'sequencenumber', 'state', 'fraction',
'timecreated', 'userid', 'name', 'value'),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo', null, 1256233700, 1, null, null),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo', null, 1256233700, 1, null, null),
));
question_bank::start_unit_test();
@@ -144,14 +144,14 @@ class question_attempt_db_test extends data_loading_method_test_base {
public function test_load_with_autosaved_data() {
$records = new question_test_recordset(array(
array('questionattemptid', 'contextid', 'questionusageid', 'slot',
- 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'flagged',
+ 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'maxfraction', 'flagged',
'questionsummary', 'rightanswer', 'responsesummary', 'timemodified',
'attemptstepid', 'sequencenumber', 'state', 'fraction',
'timecreated', 'userid', 'name', 'value'),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 4, -3, 'complete', null, 1256233715, 1, 'answer', '1'),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo', null, 1256233700, 1, null, null),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 2, 1, 'complete', null, 1256233705, 1, 'answer', '1'),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1, '', '', '', 1256233790, 3, 2, 'complete', null, 1256233710, 1, 'answer', '0'),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 4, -3, 'complete', null, 1256233715, 1, 'answer', '1'),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo', null, 1256233700, 1, null, null),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 2, 1, 'complete', null, 1256233705, 1, 'answer', '1'),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 1, '', '', '', 1256233790, 3, 2, 'complete', null, 1256233710, 1, 'answer', '0'),
));
$question = test_question_maker::make_question('truefalse', 'true');
@@ -203,15 +203,15 @@ class question_attempt_db_test extends data_loading_method_test_base {
// There is also a second lot of redundant data to delete.
$records = new question_test_recordset(array(
array('questionattemptid', 'contextid', 'questionusageid', 'slot',
- 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'flagged',
+ 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'maxfraction', 'flagged',
'questionsummary', 'rightanswer', 'responsesummary', 'timemodified',
'attemptstepid', 'sequencenumber', 'state', 'fraction',
'timecreated', 'userid', 'name', 'value'),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 5, -2, 'complete', null, 1256233715, 1, 'answer', '0'),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 4, -1, 'complete', null, 1256233715, 1, 'answer', '0'),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo', null, 1256233700, 1, null, null),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 2, 1, 'complete', null, 1256233705, 1, 'answer', '1'),
- array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1, '', '', '', 1256233790, 3, 2, 'complete', null, 1256233710, 1, 'answer', '0'),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 5, -2, 'complete', null, 1256233715, 1, 'answer', '0'),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 4, -1, 'complete', null, 1256233715, 1, 'answer', '0'),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo', null, 1256233700, 1, null, null),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 2, 1, 'complete', null, 1256233705, 1, 'answer', '1'),
+ array(1, 123, 1, 1, 'deferredfeedback', -1, 1, 2.0000000, 0.0000000, 1.0000000, 1, '', '', '', 1256233790, 3, 2, 'complete', null, 1256233710, 1, 'answer', '0'),
));
$question = test_question_maker::make_question('truefalse', 'true');
diff --git a/question/engine/tests/questionattempt_with_steps_test.php b/question/engine/tests/questionattempt_with_steps_test.php
index 72553fa3d1b..b5f1d4b729e 100644
--- a/question/engine/tests/questionattempt_with_steps_test.php
+++ b/question/engine/tests/questionattempt_with_steps_test.php
@@ -146,7 +146,7 @@ class question_attempt_with_steps_test extends advanced_testcase {
public function test_get_min_fraction() {
$this->qa->set_min_fraction(-1);
- $this->assertEquals(-1, $this->qa->get_min_fraction(0));
+ $this->assertEquals(-1, $this->qa->get_min_fraction());
}
public function test_cannot_get_min_fraction_before_start() {
@@ -154,4 +154,15 @@ class question_attempt_with_steps_test extends advanced_testcase {
$this->setExpectedException('moodle_exception');
$qa->get_min_fraction();
}
+
+ public function test_get_max_fraction() {
+ $this->qa->set_max_fraction(2);
+ $this->assertEquals(2, $this->qa->get_max_fraction());
+ }
+
+ public function test_cannot_get_max_fraction_before_start() {
+ $qa = new question_attempt($this->question, 0);
+ $this->setExpectedException('moodle_exception');
+ $qa->get_max_fraction();
+ }
}
diff --git a/question/engine/tests/questioncbm_test.php b/question/engine/tests/questioncbm_test.php
deleted file mode 100644
index 1757fd82f5c..00000000000
--- a/question/engine/tests/questioncbm_test.php
+++ /dev/null
@@ -1,48 +0,0 @@
-.
-
-/**
- * This file contains tests for the question_cbm class.
- *
- * @package moodlecore
- * @subpackage questionengine
- * @copyright 2009 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-
-defined('MOODLE_INTERNAL') || die();
-
-global $CFG;
-require_once(dirname(__FILE__) . '/../lib.php');
-
-
-/**
- * Unit tests for the question_cbm class.
- *
- * @copyright 2009 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class question_cbm_test extends advanced_testcase {
- public function test_adjust_fraction() {
- $this->assertEquals(0, question_cbm::adjust_fraction(0, question_cbm::LOW), '', 0.0000001);
- $this->assertEquals(-2/3, question_cbm::adjust_fraction(0, question_cbm::MED), '', 0.0000001);
- $this->assertEquals(-2, question_cbm::adjust_fraction(0, question_cbm::HIGH), '', 0.0000001);
- $this->assertEquals(1/3, question_cbm::adjust_fraction(1, question_cbm::LOW), '', 0.0000001);
- $this->assertEquals(2/3, question_cbm::adjust_fraction(1, question_cbm::MED), '', 0.0000001);
- $this->assertEquals(1, question_cbm::adjust_fraction(1, question_cbm::HIGH), '', 0.0000001);
- }
-}
\ No newline at end of file
diff --git a/question/engine/tests/questionusagebyactivity_test.php b/question/engine/tests/questionusagebyactivity_test.php
index 4caef125c3f..a268c86fec6 100644
--- a/question/engine/tests/questionusagebyactivity_test.php
+++ b/question/engine/tests/questionusagebyactivity_test.php
@@ -173,13 +173,13 @@ class question_usage_db_test extends data_loading_method_test_base {
$records = new question_test_recordset(array(
array('qubaid', 'contextid', 'component', 'preferredbehaviour',
'questionattemptid', 'questionusageid', 'slot',
- 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'flagged',
+ 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'maxfraction', 'flagged',
'questionsummary', 'rightanswer', 'responsesummary', 'timemodified',
'attemptstepid', 'sequencenumber', 'state', 'fraction',
'timecreated', 'userid', 'name', 'value'),
- array(1, $scid, 'unit_test', 'interactive', 1, 1, 1, 'interactive', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo', null, 1256233700, 1, null, null),
- array(1, $scid, 'unit_test', 'interactive', 1, 1, 1, 'interactive', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo', null, 1256233705, 1, 'answer', '1'),
- array(1, $scid, 'unit_test', 'interactive', 1, 1, 1, 'interactive', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 5, 2, 'gradedright', 1.0000000, 1256233720, 1, '-finish', '1'),
+ array(1, $scid, 'unit_test', 'interactive', 1, 1, 1, 'interactive', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo', null, 1256233700, 1, null, null),
+ array(1, $scid, 'unit_test', 'interactive', 1, 1, 1, 'interactive', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo', null, 1256233705, 1, 'answer', '1'),
+ array(1, $scid, 'unit_test', 'interactive', 1, 1, 1, 'interactive', -1, 1, 2.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 5, 2, 'gradedright', 1.0000000, 1256233720, 1, '-finish', '1'),
));
$question = test_question_maker::make_question('truefalse', 'true');
@@ -231,13 +231,13 @@ class question_usage_db_test extends data_loading_method_test_base {
$records = new question_test_recordset(array(
array('qubaid', 'contextid', 'component', 'preferredbehaviour',
'questionattemptid', 'questionusageid', 'slot',
- 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'flagged',
+ 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'maxfraction', 'flagged',
'questionsummary', 'rightanswer', 'responsesummary', 'timemodified',
'attemptstepid', 'sequencenumber', 'state', 'fraction',
'timecreated', 'userid', 'name', 'value'),
- array(1, $scid, 'unit_test', 'interactive', 1, 1, 1, 'interactive', 0, 1, 1.0000000, 0.0000000, 0, 'This question is missing. Unable to display anything.', '', '', 0, null, null, null, null, null, null, null, null),
- array(1, $scid, 'unit_test', 'interactive', 2, 1, 2, 'interactive', 0, 1, 1.0000000, 0.0000000, 0, 'This question is missing. Unable to display anything.', '', '', 0, null, null, null, null, null, null, null, null),
- array(1, $scid, 'unit_test', 'interactive', 3, 1, 3, 'interactive', 0, 1, 1.0000000, 0.0000000, 0, 'This question is missing. Unable to display anything.', '', '', 0, null, null, null, null, null, null, null, null),
+ array(1, $scid, 'unit_test', 'interactive', 1, 1, 1, 'interactive', 0, 1, 1.0000000, 0.0000000, 1.0000000, 0, 'This question is missing. Unable to display anything.', '', '', 0, null, null, null, null, null, null, null, null),
+ array(1, $scid, 'unit_test', 'interactive', 2, 1, 2, 'interactive', 0, 1, 1.0000000, 0.0000000, 1.0000000, 0, 'This question is missing. Unable to display anything.', '', '', 0, null, null, null, null, null, null, null, null),
+ array(1, $scid, 'unit_test', 'interactive', 3, 1, 3, 'interactive', 0, 1, 1.0000000, 0.0000000, 1.0000000, 0, 'This question is missing. Unable to display anything.', '', '', 0, null, null, null, null, null, null, null, null),
));
question_bank::start_unit_test();
@@ -263,11 +263,11 @@ class question_usage_db_test extends data_loading_method_test_base {
$records = new question_test_recordset(array(
array('qubaid', 'contextid', 'component', 'preferredbehaviour',
'questionattemptid', 'questionusageid', 'slot',
- 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'flagged',
+ 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'maxfraction', 'flagged',
'questionsummary', 'rightanswer', 'responsesummary', 'timemodified',
'attemptstepid', 'sequencenumber', 'state', 'fraction',
'timecreated', 'userid', 'name', 'value'),
- array(1, $scid, 'unit_test', 'interactive', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null),
+ array(1, $scid, 'unit_test', 'interactive', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null),
));
question_bank::start_unit_test();
diff --git a/question/engine/tests/unitofwork_test.php b/question/engine/tests/unitofwork_test.php
index 0b0b8b55d5e..17cec0d3660 100644
--- a/question/engine/tests/unitofwork_test.php
+++ b/question/engine/tests/unitofwork_test.php
@@ -82,17 +82,17 @@ class question_engine_unit_of_work_test extends data_loading_method_test_base {
return array(
array('qubaid', 'contextid', 'component', 'preferredbehaviour',
'questionattemptid', 'contextid', 'questionusageid', 'slot',
- 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'flagged',
+ 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'maxfraction', 'flagged',
'questionsummary', 'rightanswer', 'responsesummary', 'timemodified',
'attemptstepid', 'sequencenumber', 'state', 'fraction',
'timecreated', 'userid', 'name', 'value'),
- array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo', null, 1256233700, 1, '-_triesleft', 3),
- array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo', null, 1256233720, 1, 'answer', 'toad'),
- array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo', null, 1256233720, 1, '-submit', 1),
- array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo', null, 1256233720, 1, '-_triesleft', 1),
- array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 0, '', '', '', 1256233790, 3, 2, 'todo', null, 1256233740, 1, '-tryagain', 1),
- array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 0, '', '', '', 1256233790, 5, 3, 'gradedright', null, 1256233790, 1, 'answer', 'frog'),
- array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 0, '', '', '', 1256233790, 5, 3, 'gradedright', 1.0000000, 1256233790, 1, '-submit', 1),
+ array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo', null, 1256233700, 1, '-_triesleft', 3),
+ array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo', null, 1256233720, 1, 'answer', 'toad'),
+ array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo', null, 1256233720, 1, '-submit', 1),
+ array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo', null, 1256233720, 1, '-_triesleft', 1),
+ array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 3, 2, 'todo', null, 1256233740, 1, '-tryagain', 1),
+ array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 5, 3, 'gradedright', null, 1256233790, 1, 'answer', 'frog'),
+ array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 5, 3, 'gradedright', 1.0000000, 1256233790, 1, '-submit', 1),
);
}
diff --git a/question/engine/upgrade.txt b/question/engine/upgrade.txt
new file mode 100644
index 00000000000..04d1f23b39b
--- /dev/null
+++ b/question/engine/upgrade.txt
@@ -0,0 +1,16 @@
+This files describes API changes for the core question system.
+
+
+=== 2.4 ===
+
+1) The method question_behaviour::is_manual_grade_in_range and move and become
+question_engine::is_manual_grade_in_range.
+
+2) The arguments to core_question_renderer::mark_summary changed from
+($qa, $options) to ($qa, $behaviouroutput, $options). If you have overridden
+that method you will need to update your code.
+
+
+=== Earlier changes ===
+
+* Were not documented in this way. Sorry.
diff --git a/question/engine/upgrade/behaviourconverters.php b/question/engine/upgrade/behaviourconverters.php
index 5807c02259d..c6bffa0afbe 100644
--- a/question/engine/upgrade/behaviourconverters.php
+++ b/question/engine/upgrade/behaviourconverters.php
@@ -137,6 +137,7 @@ abstract class question_behaviour_attempt_updater {
$qa->rightanswer = $this->qtypeupdater->right_answer($this->question);
$qa->maxmark = $this->question->maxmark;
$qa->minfraction = 0;
+ $qa->maxfraction = 1;
$qa->flagged = 0;
$qa->responsesummary = '';
$qa->timemodified = 0;
diff --git a/question/preview.php b/question/preview.php
index 7287a5cb154..f8de4eeb31e 100644
--- a/question/preview.php
+++ b/question/preview.php
@@ -226,6 +226,7 @@ $technical = array();
$technical[] = get_string('behaviourbeingused', 'question',
question_engine::get_behaviour_name($qa->get_behaviour_name()));
$technical[] = get_string('technicalinfominfraction', 'question', $qa->get_min_fraction());
+$technical[] = get_string('technicalinfomaxfraction', 'question', $qa->get_max_fraction());
$technical[] = get_string('technicalinfoquestionsummary', 'question', s($qa->get_question_summary()));
$technical[] = get_string('technicalinforightsummary', 'question', s($qa->get_right_answer_summary()));
$technical[] = get_string('technicalinfostate', 'question', '' . $qa->get_state());
diff --git a/question/type/calculated/tests/upgradelibnewqe_test.php b/question/type/calculated/tests/upgradelibnewqe_test.php
index 83e547bdab3..3dcca15561e 100644
--- a/question/type/calculated/tests/upgradelibnewqe_test.php
+++ b/question/type/calculated/tests/upgradelibnewqe_test.php
@@ -204,6 +204,7 @@ class qtype_calculated_attempt_upgrader_test extends question_attempt_upgrader_t
'variant' => 10,
'maxmark' => 1.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'What is 7.5 m + 4.9 m?
@@ -427,6 +428,7 @@ Remember to type a unit.',
'variant' => 11,
'maxmark' => 1.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'What is 5.1 m + 4.5 m?
@@ -657,6 +659,7 @@ Remember to type a unit.',
'variant' => 1,
'maxmark' => 1.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'What is 9.9 m + 2.5 m?
diff --git a/question/type/calculatedmulti/tests/upgradelibnewqe_test.php b/question/type/calculatedmulti/tests/upgradelibnewqe_test.php
index 14c07eb4a68..88bba90daf1 100644
--- a/question/type/calculatedmulti/tests/upgradelibnewqe_test.php
+++ b/question/type/calculatedmulti/tests/upgradelibnewqe_test.php
@@ -231,6 +231,7 @@ class qtype_calculatedmulti_attempt_upgrader_test extends question_attempt_upgra
'variant' => 3,
'maxmark' => 1.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'What is 4.3 + 5.4?',
'rightanswer' => '9.7',
@@ -464,6 +465,7 @@ class qtype_calculatedmulti_attempt_upgrader_test extends question_attempt_upgra
'variant' => 8,
'maxmark' => 1.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'What is 3.7 + 6.0?',
'rightanswer' => '9.7',
@@ -741,6 +743,7 @@ class qtype_calculatedmulti_attempt_upgrader_test extends question_attempt_upgra
'variant' => 7,
'maxmark' => 1.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'What is 4.4 + 8.2?',
'rightanswer' => '12.6',
diff --git a/question/type/calculatedsimple/tests/upgradelibnewqe_test.php b/question/type/calculatedsimple/tests/upgradelibnewqe_test.php
index f98be94096a..414879d8bd2 100644
--- a/question/type/calculatedsimple/tests/upgradelibnewqe_test.php
+++ b/question/type/calculatedsimple/tests/upgradelibnewqe_test.php
@@ -195,6 +195,7 @@ class qtype_calculatedsimple_attempt_upgrader_test extends question_attempt_upgr
'variant' => 7,
'maxmark' => 1.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'What is 3 + 6 ?',
'rightanswer' => '9',
@@ -405,6 +406,7 @@ class qtype_calculatedsimple_attempt_upgrader_test extends question_attempt_upgr
'variant' => 4,
'maxmark' => 1.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'What is 6.4 + 9 ?',
'rightanswer' => '15.4',
@@ -607,6 +609,7 @@ class qtype_calculatedsimple_attempt_upgrader_test extends question_attempt_upgr
'variant' => 6,
'maxmark' => 1.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'What is 6.1 + 7 ?',
'rightanswer' => '13.1',
diff --git a/question/type/description/tests/upgradelibnewqe_test.php b/question/type/description/tests/upgradelibnewqe_test.php
index cd8dcc3c44d..552269f08a0 100644
--- a/question/type/description/tests/upgradelibnewqe_test.php
+++ b/question/type/description/tests/upgradelibnewqe_test.php
@@ -157,6 +157,7 @@ class qtype_description_attempt_upgrader_test extends question_attempt_upgrader_
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'The following questions are based on the Northampton Art Gallery case study and associated web links. The questions cover artists and works that were available during the study weeks for Block 3. Some items may no longer be available on websites but will be available in the readings for Block 3.',
'rightanswer' => '',
@@ -303,6 +304,7 @@ class qtype_description_attempt_upgrader_test extends question_attempt_upgrader_
'variant' => 1,
'maxmark' => 0,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => "PERSONAL AND PROFESSIONAL INFORMATION\n\nHere we want you to enter personal information and outline your professional qualifications and experience to date.",
'rightanswer' => '',
@@ -439,6 +441,7 @@ class qtype_description_attempt_upgrader_test extends question_attempt_upgrader_
'variant' => 1,
'maxmark' => 0,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => "MUSIC TEACHING AND LEARNING \n\nIn this section we ask you to audit your knowledge, skills and understanding against the key issues covered by the non-core online and face-to-face module units. You will be invited to identify your strengths and provide evidence of your experience and understanding.\n\nIn this set of questions, you should select from the drop-down menu ‘None’, ‘Some’, ‘Good’ or ‘Strong’.",
'rightanswer' => '',
diff --git a/question/type/essay/question.php b/question/type/essay/question.php
index a9b7cc2d0b2..582a71b6206 100644
--- a/question/type/essay/question.php
+++ b/question/type/essay/question.php
@@ -43,8 +43,7 @@ class qtype_essay_question extends question_with_responses {
public $responsetemplateformat;
public function make_behaviour(question_attempt $qa, $preferredbehaviour) {
- question_engine::load_behaviour_class('manualgraded');
- return new qbehaviour_manualgraded($qa, $preferredbehaviour);
+ return question_engine::make_behaviour('manualgraded', $qa, $preferredbehaviour);
}
/**
diff --git a/question/type/essay/tests/upgradelibnewqe_test.php b/question/type/essay/tests/upgradelibnewqe_test.php
index 122d753a054..19750b680c2 100644
--- a/question/type/essay/tests/upgradelibnewqe_test.php
+++ b/question/type/essay/tests/upgradelibnewqe_test.php
@@ -218,6 +218,7 @@ class qtype_essay_attempt_upgrader_test extends question_attempt_upgrader_test_b
'variant' => 1,
'maxmark' => 2,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => "* Give two examples of facilities within XML schemas that cannot be found in Document Type Definitions (DTDs).\n_(2 marks)_",
'rightanswer' => '',
@@ -417,6 +418,7 @@ class qtype_essay_attempt_upgrader_test extends question_attempt_upgrader_test_b
'variant' => 1,
'maxmark' => 0,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => "If you answered ‘No’ to the previous question please expand on your problem here.",
'rightanswer' => '',
@@ -545,6 +547,7 @@ class qtype_essay_attempt_upgrader_test extends question_attempt_upgrader_test_b
'variant' => 1,
'maxmark' => 0,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => "If you answered ‘No’ to the previous question please expand on your problem here.",
'rightanswer' => '',
diff --git a/question/type/match/tests/upgradelibnewqe_test.php b/question/type/match/tests/upgradelibnewqe_test.php
index 3f1a53d45ec..ac76973230e 100644
--- a/question/type/match/tests/upgradelibnewqe_test.php
+++ b/question/type/match/tests/upgradelibnewqe_test.php
@@ -224,6 +224,7 @@ class qtype_match_attempt_upgrader_test extends question_attempt_upgrader_test_b
'variant' => 1,
'maxmark' => 3,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'Associate the appropriate definition with each term. ' .
'{Active adjacent system; Autonomous adjacent system; ' .
@@ -455,6 +456,7 @@ class qtype_match_attempt_upgrader_test extends question_attempt_upgrader_test_b
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'Which of the following statements about subject ' .
'gateways are true, and which are false? {Subject gateways ' .
@@ -731,6 +733,7 @@ class qtype_match_attempt_upgrader_test extends question_attempt_upgrader_test_b
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'Which of the following statements about subject gateways are true, and which are false? ' .
'{Subject gateways provide links to sites that have been quality checked; ' .
diff --git a/question/type/multianswer/question.php b/question/type/multianswer/question.php
index 3be216e8620..6e5e3c4e472 100644
--- a/question/type/multianswer/question.php
+++ b/question/type/multianswer/question.php
@@ -112,6 +112,16 @@ class qtype_multianswer_question extends question_graded_automatically_with_coun
return $fractionsum / $fractionmax;
}
+ public function get_max_fraction() {
+ $fractionsum = 0;
+ $fractionmax = 0;
+ foreach ($this->subquestions as $i => $subq) {
+ $fractionmax += $subq->defaultmark;
+ $fractionsum += $subq->defaultmark * $subq->get_max_fraction();
+ }
+ return $fractionsum / $fractionmax;
+ }
+
public function get_expected_data() {
$expected = array();
foreach ($this->subquestions as $i => $subq) {
diff --git a/question/type/multianswer/tests/upgradelibnewqe_test.php b/question/type/multianswer/tests/upgradelibnewqe_test.php
index 76504571f41..66dacff5c59 100644
--- a/question/type/multianswer/tests/upgradelibnewqe_test.php
+++ b/question/type/multianswer/tests/upgradelibnewqe_test.php
@@ -205,6 +205,7 @@ class qtype_multianswer_attempt_upgrader_test extends question_attempt_upgrader_
'variant' => 1,
'maxmark' => 1.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'An answer _____.',
'rightanswer' => 'part 1: frog',
@@ -397,6 +398,7 @@ class qtype_multianswer_attempt_upgrader_test extends question_attempt_upgrader_
'variant' => 1,
'maxmark' => 1.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'An answer _____.',
'rightanswer' => 'part 1: frog',
@@ -604,6 +606,7 @@ class qtype_multianswer_attempt_upgrader_test extends question_attempt_upgrader_
'variant' => 1,
'maxmark' => 1.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'An answer _____.',
'rightanswer' => 'part 1: frog',
@@ -1273,6 +1276,7 @@ class qtype_multianswer_attempt_upgrader_test extends question_attempt_upgrader_
'variant' => 1,
'maxmark' => '13.0000000',
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'This question consists of some text with an answer embedded right here {Wrong answer; Another wrong answer; Correct answer; Answer that gives half the credit}
@@ -1947,6 +1951,7 @@ b) What grade would you give it? _____',
'variant' => 1,
'maxmark' => 13.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'This question consists of some text with an answer embedded right here {Wrong answer; Another wrong answer; Correct answer; Answer that gives half the credit}
@@ -2641,6 +2646,7 @@ b) What grade would you give it? _____',
'variant' => 1,
'maxmark' => '13.0000000',
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'This question consists of some text with an answer embedded right here {Wrong answer; Another wrong answer; Correct answer; Answer that gives half the credit}
diff --git a/question/type/multichoice/tests/upgradelibnewqe_test.php b/question/type/multichoice/tests/upgradelibnewqe_test.php
index bc694078673..cd30bc2c6f1 100644
--- a/question/type/multichoice/tests/upgradelibnewqe_test.php
+++ b/question/type/multichoice/tests/upgradelibnewqe_test.php
@@ -239,6 +239,7 @@ class qtype_multichoice_attempt_upgrader_test extends question_attempt_upgrader_
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'Which is the amphibian?',
'rightanswer' => 'Frog',
@@ -415,6 +416,7 @@ class qtype_multichoice_attempt_upgrader_test extends question_attempt_upgrader_
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'Why were standards such an important driver in the growth of systems integration?',
'rightanswer' => 'Because they enable stored data and interfaces to be standardised.',
@@ -610,6 +612,7 @@ class qtype_multichoice_attempt_upgrader_test extends question_attempt_upgrader_
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'I have demonstrated the ability to analyse and ANSWER THE QUESTION that is asked. (Click for advice on this skill)',
'rightanswer' => '',
@@ -833,6 +836,7 @@ class qtype_multichoice_attempt_upgrader_test extends question_attempt_upgrader_
'variant' => 1,
'maxmark' => 5,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'Which of the following techniques can identify non-functional requirements?',
'rightanswer' => 'Verb identification.; Misuse cases.',
@@ -991,6 +995,7 @@ class qtype_multichoice_attempt_upgrader_test extends question_attempt_upgrader_
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'Why were standards such an important driver in the growth of systems integration?',
'rightanswer' => 'Because they enable stored data and interfaces to be standardised.',
@@ -1185,6 +1190,7 @@ public function test_multichoice_deferredfeedback_qsession140() {
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'Where will the 19th International Biology Olympiad be held?',
'rightanswer' => 'Mumbai, India',
@@ -1423,6 +1429,7 @@ public function test_multichoice_deferredfeedback_qsession140() {
'variant' => 1,
'maxmark' => 0,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'Which was the most useful K315 information literacy or ICT activity?',
'rightanswer' => 'Values Exchange case studies',
@@ -1655,6 +1662,7 @@ public function test_multichoice_deferredfeedback_qsession140() {
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => "For source 3, the 'Loose Change' video, select the statements to indicate 'yes':",
'rightanswer' => 'Would you regard this source as objective?; Does the source provide references?; Are all viewpoints considered?; Are quotations placed in their full context?; Does the source include conclusions based on evidence?; Would you trust this source?; Has it been peer-reviewed?',
@@ -1884,6 +1892,7 @@ public function test_multichoice_deferredfeedback_qsession140() {
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'Can we have a \'realist\' or objective social science?',
'rightanswer' => 'Sometimes',
@@ -2097,6 +2106,7 @@ public function test_multichoice_deferredfeedback_qsession140() {
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => "Read the text below and then answer the question based on the information given.\n\n我大哥没有我二哥高,我比我二哥高。\n\nWho is the tallest?",
'rightanswer' => '我。',
@@ -2312,6 +2322,7 @@ public function test_multichoice_deferredfeedback_qsession140() {
'variant' => 1,
'maxmark' => 5,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'What is SOAP?',
'rightanswer' => 'It is the transport mechanism used with web services.',
@@ -2521,6 +2532,7 @@ public function test_multichoice_deferredfeedback_qsession140() {
'variant' => 1,
'maxmark' => 0,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => '[CUT]',
'rightanswer' => 'Well developed',
@@ -2732,6 +2744,7 @@ public function test_multichoice_deferredfeedback_qsession140() {
'variant' => 1,
'maxmark' => 0,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => "USING THE INTERNET \n\nMany study resources are available online, and you will also be expected to find things out yourself using the web. \n\n_How would you rate your skills in using browsers and managing documents you find on the internet? _",
'rightanswer' => 'Well developed',
diff --git a/question/type/numerical/tests/upgradelibnewqe_test.php b/question/type/numerical/tests/upgradelibnewqe_test.php
index 0429480aa53..b44c1e167ba 100644
--- a/question/type/numerical/tests/upgradelibnewqe_test.php
+++ b/question/type/numerical/tests/upgradelibnewqe_test.php
@@ -229,6 +229,7 @@ class qtype_numerical_attempt_upgrader_test extends question_attempt_upgrader_te
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => "Calculate your answer to the nearest WHOLE £ and enter it in the blank space provided. \n\n[Note: Please _DO NOT_ enter commas, spaces or £ signs within the number you enter.] \n\nThe cash flows of a project are set out below: \n\n \t\tYEAR \n \t\t£ \n\n \t\tYear 0 \n \t\t(28,000) \n\n \t\tYear 1 \n \t\t18,000 \n\n \t\tYear 2 \n \t\t16,000 \n\nUsing the Table in Book 11, what is the project's net present value using a discount rate of 10%? \n\nIf it is negative, put a minus sign before the number you enter. \n\n£",
'rightanswer' => '1586',
@@ -426,6 +427,7 @@ class qtype_numerical_attempt_upgrader_test extends question_attempt_upgrader_te
'variant' => 1,
'maxmark' => 1.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'What is twice 1.5 m?',
'rightanswer' => '3 m',
diff --git a/question/type/questionbase.php b/question/type/questionbase.php
index 448d200f5bc..042afb5a86a 100644
--- a/question/type/questionbase.php
+++ b/question/type/questionbase.php
@@ -154,7 +154,7 @@ abstract class question_definition {
* one asked for. For example, you migth want to return a
* qbehaviour_interactive_adapted_for_myqtype.
*
- * @param question_attempt $qa the attempt we are creating an behaviour for.
+ * @param question_attempt $qa the attempt we are creating a behaviour for.
* @param string $preferredbehaviour the requested type of behaviour.
* @return question_behaviour the new behaviour object.
*/
@@ -231,12 +231,24 @@ abstract class question_definition {
* This method returns the lowest mark the question can return, on the
* fraction scale. that is, where the maximum possible mark is 1.0.
*
- * @return number minimum fraction this question will ever return.
+ * @return float minimum fraction this question will ever return.
*/
public function get_min_fraction() {
return 0;
}
+ /**
+ * Some questions can return a mark greater than the maximum.
+ *
+ * This method returns the lowest highest the question can return, on the
+ * fraction scale. that is, where the nominal maximum mark is 1.0.
+ *
+ * @return float maximum fraction this question will ever return.
+ */
+ public function get_max_fraction() {
+ return 1;
+ }
+
/**
* Given a response, rest the parts that are wrong.
* @param array $response a response
@@ -390,8 +402,7 @@ class question_information_item extends question_definition {
}
public function make_behaviour(question_attempt $qa, $preferredbehaviour) {
- question_engine::load_behaviour_class('informationitem');
- return new qbehaviour_informationitem($qa, $preferredbehaviour);
+ return question_engine::make_behaviour('informationitem', $qa, $preferredbehaviour);
}
public function get_expected_data() {
@@ -522,11 +533,11 @@ interface question_automatically_gradable extends question_manually_gradable {
/**
* Grade a response to the question, returning a fraction between
- * get_min_fraction() and 1.0, and the corresponding {@link question_state}
+ * get_min_fraction() and get_max_fraction(), and the corresponding {@link question_state}
* right, partial or wrong.
* @param array $response responses, as returned by
* {@link question_attempt_step::get_qt_data()}.
- * @return array (number, integer) the fraction, and the state.
+ * @return array (float, integer) the fraction, and the state.
*/
public function grade_response(array $response);
diff --git a/question/type/random/tests/upgradelibnewqe_test.php b/question/type/random/tests/upgradelibnewqe_test.php
index a76531d963e..450c10b895e 100644
--- a/question/type/random/tests/upgradelibnewqe_test.php
+++ b/question/type/random/tests/upgradelibnewqe_test.php
@@ -232,6 +232,7 @@ class qtype_random_attempt_upgrader_test extends question_attempt_upgrader_test_
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => "(Notice in the panel at the left that the question you have just done is now greyed out. Remember that you can REVISIT ANY QUESTIONS by clicking on the NUMBERED BOXES. Click Q1 now to try this out and then click Q2 to return here.) \n\n[Table 1 showing Number of households receiving home care (England)] \n\n(LINK TO RESIZABLE TABLE 1) \n\nWhich was the first year when more than a quarter of a million households received care from independent agencies?",
'rightanswer' => '2005',
@@ -473,6 +474,7 @@ class qtype_random_attempt_upgrader_test extends question_attempt_upgrader_test_
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'En France, le covoiturage est une pratique qui a été au départ préconisé par « les écolos et les verts », mais depuis, elle :',
'rightanswer' => 'n’est plus uniquement un positionnement écologiste.',
@@ -751,6 +753,7 @@ class qtype_random_attempt_upgrader_test extends question_attempt_upgrader_test_
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => "The figure below shows a graph for converting between miles and kilometres. \n\n[ The graph has a horizontal axis, labelled miles, with a scale marked from 0 to 50 in steps of 5, and a vertical axis, labelled kilometres, with a scale from 0 to 80 in steps of 10. The straight line of the graph starts at the origin and slopes up to the right, and appears to pass through the point corresponding to 25 miles and 40 kilometres, and through the point corresponding to 50 miles and 80 kilometres. ] \n\nBy taking a reading from the graph, estimate how many kilometres are equivalent to 40 miles. \n\nRound your answer to the nearest kilometre and type it in the box below. \n\n(Your answer should be a single number, without units.)",
'rightanswer' => '64 km',
@@ -948,6 +951,7 @@ class qtype_random_attempt_upgrader_test extends question_attempt_upgrader_test_
'variant' => 1,
'maxmark' => 1.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'What is twice 1.5 m?',
'rightanswer' => '3 m',
diff --git a/question/type/shortanswer/tests/upgradelibnewqe_test.php b/question/type/shortanswer/tests/upgradelibnewqe_test.php
index 162e294ba1b..be6e46c8012 100644
--- a/question/type/shortanswer/tests/upgradelibnewqe_test.php
+++ b/question/type/shortanswer/tests/upgradelibnewqe_test.php
@@ -213,6 +213,7 @@ class qtype_shortanswer_attempt_upgrader_test extends question_attempt_upgrader_
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => "Complete the following sentence. \n\nIn general, the procedures for analysing cost data for decision making are: \n\nIgnore all sunk costs, ignore all ______ costs, use remaining costs for decision making purposes.",
'rightanswer' => 'unavoidable*',
@@ -395,6 +396,7 @@ class qtype_shortanswer_attempt_upgrader_test extends question_attempt_upgrader_
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'In information-oriented integration what is the document which describes all the data structures in a potential integrated system and their relationships, for example, the fact that one database contained on a server will consist of a subset of another database contained in another server?',
'rightanswer' => '*Enterprise data model*',
@@ -595,6 +597,7 @@ class qtype_shortanswer_attempt_upgrader_test extends question_attempt_upgrader_
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => "Complete the following sentence by providing an appropriate word for the indicated gap. \n\nCapital investment appraisal involves both quantitative and ______ issues.",
'rightanswer' => 'qualitative*',
diff --git a/question/type/truefalse/tests/upgradelibnewqe_test.php b/question/type/truefalse/tests/upgradelibnewqe_test.php
index 315ec369387..36f632bb0c9 100644
--- a/question/type/truefalse/tests/upgradelibnewqe_test.php
+++ b/question/type/truefalse/tests/upgradelibnewqe_test.php
@@ -193,6 +193,7 @@ class qtype_truefalse_attempt_upgrader_test extends question_attempt_upgrader_te
'variant' => 1,
'maxmark' => 5,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'The term ‘integration server’ is another name for an application server, true or false?',
'rightanswer' => 'False',
@@ -380,6 +381,7 @@ class qtype_truefalse_attempt_upgrader_test extends question_attempt_upgrader_te
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => "[my market] \n\nWhat can you buy in this shop? Is this list accurate? \n\nMark true or false (for the list as a whole). \n\n_single tickets_ \n\n_weekly season tickets_ \n\n_monthly season tickets_ \n\n_wine and grappa_ \n\n_fruit and vegetables_ \n\n_tobacco _",
'rightanswer' => 'False',
@@ -549,6 +551,7 @@ class qtype_truefalse_attempt_upgrader_test extends question_attempt_upgrader_te
'variant' => 1,
'maxmark' => 1,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => 'Web services, integration servers, XML, application servers, message-oriented middleware and remote procedure call can be used to enable integrated systems?',
'rightanswer' => 'True',
@@ -738,6 +741,7 @@ class qtype_truefalse_attempt_upgrader_test extends question_attempt_upgrader_te
'variant' => 1,
'maxmark' => 10.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => '1 +1 = 2 ?',
'rightanswer' => 'True',
@@ -929,6 +933,7 @@ class qtype_truefalse_attempt_upgrader_test extends question_attempt_upgrader_te
'variant' => 1,
'maxmark' => 10.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => '1 +1 = 2 ?',
'rightanswer' => 'True',
@@ -1125,6 +1130,7 @@ class qtype_truefalse_attempt_upgrader_test extends question_attempt_upgrader_te
'variant' => 1,
'maxmark' => 1.0000000,
'minfraction' => 0,
+ 'maxfraction' => 1,
'flagged' => 0,
'questionsummary' => '',
'rightanswer' => 'True',
diff --git a/version.php b/version.php
index 9415705e4bb..9b6dbc9229e 100644
--- a/version.php
+++ b/version.php
@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
-$version = 2013100400.01; // YYYYMMDD = weekly release date of this DEV branch.
+$version = 2013100800.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.