Merge branch 'MDL-32188' of git://github.com/timhunt/moodle

Conflicts:
	lib/db/upgrade.php
This commit is contained in:
Sam Hemelryk 2013-10-08 14:43:36 +13:00
commit cbf34f34cc
74 changed files with 1680 additions and 298 deletions

View File

@ -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' => '',

View File

@ -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'));

View File

@ -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);

View File

@ -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}';

View File

@ -1392,6 +1392,7 @@
<FIELD NAME="variant" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="The variant of the qusetion being used."/>
<FIELD NAME="maxmark" TYPE="number" LENGTH="12" NOTNULL="true" SEQUENCE="false" DECIMALS="7" COMMENT="The grade this question is marked out of in this attempt."/>
<FIELD NAME="minfraction" TYPE="number" LENGTH="12" NOTNULL="true" SEQUENCE="false" DECIMALS="7" COMMENT="Some questions can award negative marks. This indicates the most negative mark that can be awarded, on the faction scale where the maximum positive mark is 1."/>
<FIELD NAME="maxfraction" TYPE="number" LENGTH="12" NOTNULL="true" DEFAULT="1" SEQUENCE="false" DECIMALS="7" COMMENT="Some questions can give fractions greater than 1. This indicates the greatest fraction that can be awarded."/>
<FIELD NAME="flagged" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether this question has been flagged within the attempt."/>
<FIELD NAME="questionsummary" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="If this question uses randomisation, it should set this field to summarise what random version the student actually saw. This is a human-readable textual summary of the student's response which might, for example, be used in a report."/>
<FIELD NAME="rightanswer" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="This is a human-readable textual summary of the right answer to this question. Might be used, for example on the quiz preview, to help people who are testing the question. Or might be used in reports."/>

View File

@ -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;

View File

@ -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.

View File

@ -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();

View File

@ -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';

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -0,0 +1,39 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Question 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;
}
}

View File

@ -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;
}

View File

@ -0,0 +1,38 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Question 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 {
}

View File

@ -15,7 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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];
}
}

View File

@ -0,0 +1,144 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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);
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1,241 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Question 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);
}
}

View File

@ -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';

View File

@ -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));
}
}

View File

@ -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;
}

View File

@ -0,0 +1,142 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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));
}
}

View File

@ -0,0 +1,56 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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);
}
}

View File

@ -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));
}

View File

@ -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();
}

View File

@ -0,0 +1,44 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Question 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');
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1,42 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Question 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();
}
}

View File

@ -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);

View File

@ -0,0 +1,39 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Question 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;
}
}

View File

@ -15,7 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This behaviour is for informaiton items.
* This behaviour is for information items.
*
* @package qbehaviour
* @subpackage informationitem

View File

@ -0,0 +1,36 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Question 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 {
}

View File

@ -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.

View File

@ -0,0 +1,39 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Question 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;
}
}

View File

@ -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;

View File

@ -0,0 +1,41 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Question 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;
}
}

View File

@ -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);

View File

@ -0,0 +1,44 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Question 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');
}
}

View File

@ -15,7 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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.');
}
}

View File

@ -0,0 +1,38 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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 {
}

View File

@ -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'),
));

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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,

View File

@ -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.
*/

View File

@ -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;

View File

@ -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) {

View File

@ -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.
*/

View File

@ -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);
}
/**

View File

@ -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;
}

View File

@ -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');

View File

@ -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();
}
}

View File

@ -1,48 +0,0 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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);
}
}

View File

@ -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();

View File

@ -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),
);
}

View File

@ -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.

View File

@ -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;

View File

@ -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());

View File

@ -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?

View File

@ -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',

View File

@ -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',

View File

@ -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' => '',

View File

@ -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);
}
/**

View File

@ -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' => '',

View File

@ -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; ' .

View File

@ -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) {

View File

@ -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}

View File

@ -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',

View File

@ -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',

View File

@ -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);

View File

@ -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' => 'nest 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',

View File

@ -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*',

View File

@ -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',

View File

@ -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.