mirror of
https://github.com/moodle/moodle.git
synced 2025-03-19 23:20:09 +01:00
MDL-79863 qtype_ordering: qtype_ordering fix grade calculation for RELATIVE_ONE_PREVIOUS_AND_NEXT and RELATIVE_ALL_PREVIOUS_AND_NEXT, and add grade/score details for less than perfect grades
This commit is contained in:
parent
c2e0d67318
commit
46abdfc86e
@ -21,6 +21,7 @@ $string['answer'] = 'Item text';
|
||||
$string['answerheader'] = 'Draggable item {no}';
|
||||
$string['correctorder'] = 'The correct order for these items is as follows:';
|
||||
$string['defaultquestionname'] = 'Drag the following items into the correct order.';
|
||||
$string['gradedetails'] = 'Grade details';
|
||||
$string['gradingtype'] = 'Grading type';
|
||||
$string['gradingtype_help'] = 'Choose the type of grading calculation.
|
||||
|
||||
@ -49,6 +50,7 @@ $string['relativenextincludelast'] = 'Relative to the next item (including last)
|
||||
$string['relativeonepreviousandnext'] = 'Relative to both the previous and next items';
|
||||
$string['removeeditor'] = 'Remove HTML editor';
|
||||
$string['removeitem'] = 'Remove draggable item';
|
||||
$string['scoredetails'] = 'Here are the scores for each item in this response:';
|
||||
$string['selectall'] = 'Select all items';
|
||||
$string['selectcontiguous'] = 'Select a contiguous subset of items';
|
||||
$string['selectcount_help'] = 'The number of items that will be displayed when the question is appears in a quiz.';
|
||||
|
@ -190,18 +190,14 @@ class qtype_ordering_question extends question_graded_automatically {
|
||||
foreach ($correctresponse as $thisanswerid => $answerids) {
|
||||
if (isset($currentresponse[$thisanswerid])) {
|
||||
$prev = $currentresponse[$thisanswerid]->prev;
|
||||
$prev = array_intersect($answerids->prev, $prev);
|
||||
$prev = array_intersect($prev, $answerids->prev);
|
||||
$countcorrect += count($prev);
|
||||
$next = $currentresponse[$thisanswerid]->next;
|
||||
$next = array_intersect($answerids->next, $next);
|
||||
$next = array_intersect($next, $answerids->next);
|
||||
$countcorrect += count($next);
|
||||
}
|
||||
$countanswers++;
|
||||
}
|
||||
if ($options->gradingtype) {
|
||||
$countanswers *= 2;
|
||||
} else {
|
||||
$countanswers *= (count($correctresponse) - 1);
|
||||
$countanswers += count($answerids->prev);
|
||||
$countanswers += count($answerids->next);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -38,6 +38,10 @@ if (! class_exists('qtype_with_combined_feedback_renderer')) { // Moodle 2.0
|
||||
*/
|
||||
class qtype_ordering_renderer extends qtype_with_combined_feedback_renderer {
|
||||
|
||||
protected $correctinfo = null;
|
||||
protected $currentinfo = null;
|
||||
protected $itemscores = array();
|
||||
|
||||
public function formulation_and_controls(question_attempt $qa, question_display_options $options) {
|
||||
global $CFG, $DB;
|
||||
|
||||
@ -102,31 +106,9 @@ class qtype_ordering_renderer extends qtype_with_combined_feedback_renderer {
|
||||
// set layout class
|
||||
$layoutclass = $question->get_ordering_layoutclass();
|
||||
|
||||
// get info about current/correct responses
|
||||
if ($options->correctness) {
|
||||
switch ($question->options->gradingtype) {
|
||||
|
||||
case 0: // ABSOLUTE
|
||||
$correctinfo = $correctresponse;
|
||||
$currentinfo = $currentresponse;
|
||||
break;
|
||||
|
||||
case 1: // RELATIVE_NEXT_EXCLUDE_LAST
|
||||
case 2: // RELATIVE_NEXT_INCLUDE_LAST
|
||||
$currentinfo = $question->get_next_answerids($currentresponse, ($question->options->gradingtype==2));
|
||||
$correctinfo = $question->get_next_answerids($correctresponse, ($question->options->gradingtype==2));
|
||||
break;
|
||||
|
||||
case 3: // RELATIVE_ONE_PREVIOUS_AND_NEXT
|
||||
case 4: // RELATIVE_ALL_PREVIOUS_AND_NEXT
|
||||
$currentinfo = $question->get_previous_and_next_answerids($currentresponse, ($question->options->gradingtype==4));
|
||||
$correctinfo = $question->get_previous_and_next_answerids($correctresponse, ($question->options->gradingtype==4));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// generate ordering items
|
||||
foreach ($currentresponse as $position => $answerid) {
|
||||
|
||||
if (! array_key_exists($answerid, $question->answers)) {
|
||||
continue; // shouldn't happen !!
|
||||
}
|
||||
@ -141,71 +123,13 @@ class qtype_ordering_renderer extends qtype_with_combined_feedback_renderer {
|
||||
$result .= html_writer::start_tag('ul', array('class' => 'sortablelist', 'id' => $sortable_id));
|
||||
}
|
||||
|
||||
// CSS $class and $img are only used to show correctness
|
||||
$class = '';
|
||||
$img = '';
|
||||
|
||||
// display the correctness of this item
|
||||
// set the CSS class and correctness img for this response
|
||||
if ($options->correctness) {
|
||||
|
||||
// correctness depends on grading type
|
||||
$score = 0; // actual score for this item
|
||||
$maxscore = null; // maximum score for this item
|
||||
switch ($question->options->gradingtype) {
|
||||
|
||||
case 0: // ABSOLUTE
|
||||
if (isset($correctinfo[$position])) {
|
||||
if ($correctinfo[$position]==$answerid) {
|
||||
$score = 1;
|
||||
}
|
||||
$maxscore = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1; // RELATIVE_NEXT_EXCLUDE_LAST
|
||||
case 2; // RELATIVE_NEXT_INCLUDE_LAST
|
||||
if (isset($correctinfo[$answerid])) {
|
||||
if (isset($currentinfo[$answerid]) && $currentinfo[$answerid]==$correctinfo[$answerid]) {
|
||||
$score = 1;
|
||||
}
|
||||
$maxscore = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3; // RELATIVE_ONE_PREVIOUS_AND_NEXT
|
||||
case 4; // RELATIVE_ALL_PREVIOUS_AND_NEXT
|
||||
if (isset($correctinfo[$answerid])) {
|
||||
$maxscore = 0;
|
||||
$prev = $correctinfo[$answerid]->prev;
|
||||
$maxscore += count($prev);
|
||||
$prev = array_intersect($prev, $currentinfo[$answerid]->prev);
|
||||
$score += count($prev);
|
||||
$next = $correctinfo[$answerid]->next;
|
||||
$maxscore += count($next);
|
||||
$next = array_intersect($next, $currentinfo[$answerid]->next);
|
||||
$score += count($next);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ($maxscore===null) {
|
||||
$class = 'unscored';
|
||||
} else {
|
||||
if ($maxscore==0) {
|
||||
$score = 0.0;
|
||||
} else {
|
||||
$score = ($score / $maxscore);
|
||||
}
|
||||
switch (true) {
|
||||
case ($score > 0.999999): $class = 'correct'; break;
|
||||
case ($score < 0.000001): $class = 'incorrect'; break;
|
||||
case ($score >= 0.66): $class = 'partial66'; break;
|
||||
case ($score >= 0.33): $class = 'partial33'; break;
|
||||
default: $class = 'partial01'; break;
|
||||
}
|
||||
$img = $this->feedback_image($score).' ';
|
||||
}
|
||||
$score = $this->get_ordering_item_score($question, $position, $answerid);
|
||||
list($score, $maxscore, $fraction, $percent, $class, $img) = $score;
|
||||
} else {
|
||||
$class = 'sortableitem';
|
||||
$img = '';
|
||||
}
|
||||
$class = "$class $layoutclass";
|
||||
|
||||
@ -236,7 +160,73 @@ class qtype_ordering_renderer extends qtype_with_combined_feedback_renderer {
|
||||
}
|
||||
|
||||
public function specific_feedback(question_attempt $qa) {
|
||||
return $this->combined_feedback($qa);
|
||||
|
||||
if ($feedback = $this->combined_feedback($qa)) {
|
||||
$feedback = html_writer::tag('p', $feedback);
|
||||
}
|
||||
|
||||
$gradingtype = '';
|
||||
$gradedetails = '';
|
||||
$scoredetails = '';
|
||||
|
||||
// if required, add explanation of grade calculation
|
||||
if ($step = $qa->get_last_step()) {
|
||||
$state = $step->get_state();
|
||||
if ($state=='gradedpartial' || $state=='gradedwrong') {
|
||||
|
||||
$plugin = 'qtype_ordering';
|
||||
$question = $qa->get_question();
|
||||
|
||||
// fetch grading type
|
||||
switch ($question->options->gradingtype) {
|
||||
case 0: $gradingtype = get_string('absoluteposition', $plugin); break;
|
||||
case 1: $gradingtype = get_string('relativenextexcludelast', $plugin); break;
|
||||
case 2: $gradingtype = get_string('relativenextincludelast', $plugin); break;
|
||||
case 3: $gradingtype = get_string('relativeonepreviousandnext', $plugin); break;
|
||||
case 4: $gradingtype = get_string('relativeallpreviousandnext', $plugin); break;
|
||||
}
|
||||
|
||||
// format grading type, e.g. Grading type: Relative to next item, excluding last item
|
||||
if ($gradingtype) {
|
||||
$gradingtype = get_string('gradingtype', $plugin).': '.$gradingtype;
|
||||
$gradingtype = html_writer::tag('p', $gradingtype, array('class' => 'gradingtype'));
|
||||
}
|
||||
|
||||
// fetch grade details and score details
|
||||
if ($currentresponse = $question->currentresponse) {
|
||||
|
||||
$totalscore = 0;
|
||||
$totalmaxscore = 0;
|
||||
|
||||
$layoutclass = $question->get_ordering_layoutclass();
|
||||
$params = array('class' => $layoutclass);
|
||||
|
||||
$scoredetails .= html_writer::tag('p', get_string('scoredetails', $plugin));
|
||||
$scoredetails .= html_writer::start_tag('ol', array('class' => 'scoredetails'));
|
||||
|
||||
// format scoredetails, e.g. 1 /2 = 50%, for each item
|
||||
foreach ($currentresponse as $position => $answerid) {
|
||||
$answer = $question->answers[$answerid];
|
||||
$score = $this->get_ordering_item_score($question, $position, $answerid);
|
||||
list($score, $maxscore, $fraction, $percent, $class, $img) = $score;
|
||||
$totalscore += $score;
|
||||
$totalmaxscore += $maxscore;
|
||||
$score = "$score / $maxscore = $percent%";
|
||||
$scoredetails .= html_writer::tag('li', $score, $params);
|
||||
}
|
||||
|
||||
$scoredetails .= html_writer::end_tag('ol');
|
||||
|
||||
// format gradedetails, e.g. 4 /6 = 67%
|
||||
$gradedetails = round(100 * $totalscore / $totalmaxscore, 0);
|
||||
$gradedetails = "$totalscore / $totalmaxscore = $gradedetails%";
|
||||
$gradedetails = get_string('gradedetails', $plugin).': '.$gradedetails;
|
||||
$gradedetails = html_writer::tag('p', $gradedetails, array('class' => 'gradedetails'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $feedback.$gradingtype.$gradedetails.$scoredetails;
|
||||
}
|
||||
|
||||
public function correct_response(question_attempt $qa) {
|
||||
@ -271,4 +261,119 @@ class qtype_ordering_renderer extends qtype_with_combined_feedback_renderer {
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
// custom methods
|
||||
/////////////////////////////////////
|
||||
|
||||
protected function get_response_info($question) {
|
||||
switch ($question->options->gradingtype) {
|
||||
|
||||
case 0: // ABSOLUTE
|
||||
$this->correctinfo = $question->correctresponse;
|
||||
$this->currentinfo = $question->currentresponse;
|
||||
break;
|
||||
|
||||
case 1: // RELATIVE_NEXT_EXCLUDE_LAST
|
||||
$this->correctinfo = $question->get_next_answerids($question->correctresponse, false);
|
||||
$this->currentinfo = $question->get_next_answerids($question->currentresponse, false);
|
||||
break;
|
||||
case 2: // RELATIVE_NEXT_INCLUDE_LAST
|
||||
$this->correctinfo = $question->get_next_answerids($question->correctresponse, true);
|
||||
$this->currentinfo = $question->get_next_answerids($question->currentresponse, true);
|
||||
break;
|
||||
|
||||
case 3: // RELATIVE_ONE_PREVIOUS_AND_NEXT
|
||||
$this->correctinfo = $question->get_previous_and_next_answerids($question->correctresponse, false);
|
||||
$this->currentinfo = $question->get_previous_and_next_answerids($question->currentresponse, false);
|
||||
break;
|
||||
|
||||
case 4: // RELATIVE_ALL_PREVIOUS_AND_NEXT
|
||||
$this->correctinfo = $question->get_previous_and_next_answerids($question->correctresponse, true);
|
||||
$this->currentinfo = $question->get_previous_and_next_answerids($question->currentresponse, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function get_ordering_item_score($question, $position, $answerid) {
|
||||
|
||||
if (! isset($this->itemscores[$position])) {
|
||||
|
||||
if ($this->correctinfo===null || $this->currentinfo===null) {
|
||||
$this->get_response_info($question);
|
||||
}
|
||||
|
||||
$correctinfo = $this->correctinfo;
|
||||
$currentinfo = $this->currentinfo;
|
||||
|
||||
$score = 0; // actual score for this item
|
||||
$maxscore = null; // max score for this item
|
||||
$fraction = 0.0; // $score / $maxscore
|
||||
$percent = 0; // 100 * $fraction
|
||||
|
||||
switch ($question->options->gradingtype) {
|
||||
|
||||
case 0: // ABSOLUTE
|
||||
if (isset($correctinfo[$position])) {
|
||||
if ($correctinfo[$position]==$answerid) {
|
||||
$score = 1;
|
||||
}
|
||||
$maxscore = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1; // RELATIVE_NEXT_EXCLUDE_LAST
|
||||
case 2; // RELATIVE_NEXT_INCLUDE_LAST
|
||||
if (isset($correctinfo[$answerid])) {
|
||||
if (isset($currentinfo[$answerid]) && $currentinfo[$answerid]==$correctinfo[$answerid]) {
|
||||
$score = 1;
|
||||
}
|
||||
$maxscore = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3; // RELATIVE_ONE_PREVIOUS_AND_NEXT
|
||||
case 4; // RELATIVE_ALL_PREVIOUS_AND_NEXT
|
||||
if (isset($correctinfo[$answerid])) {
|
||||
$maxscore = 0;
|
||||
$prev = $correctinfo[$answerid]->prev;
|
||||
$maxscore += count($prev);
|
||||
$prev = array_intersect($prev, $currentinfo[$answerid]->prev);
|
||||
$score += count($prev);
|
||||
$next = $correctinfo[$answerid]->next;
|
||||
$maxscore += count($next);
|
||||
$next = array_intersect($next, $currentinfo[$answerid]->next);
|
||||
$score += count($next);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if ($maxscore===null) {
|
||||
// an unscored item is either an illegal item
|
||||
// or last item of RELATIVE_NEXT_EXCLUDE_LAST
|
||||
$class = 'unscored';
|
||||
} else {
|
||||
if ($maxscore==0) {
|
||||
$fraction = 0.0;
|
||||
$percent = 0;
|
||||
} else {
|
||||
$fraction = ($score / $maxscore);
|
||||
$percent = round(100 * $fraction, 0);
|
||||
}
|
||||
switch (true) {
|
||||
case ($fraction > 0.999999): $class = 'correct'; break;
|
||||
case ($fraction < 0.000001): $class = 'incorrect'; break;
|
||||
case ($fraction >= 0.66): $class = 'partial66'; break;
|
||||
case ($fraction >= 0.33): $class = 'partial33'; break;
|
||||
default: $class = 'partial00'; break;
|
||||
}
|
||||
$img = $this->feedback_image($fraction);
|
||||
}
|
||||
|
||||
$score = array($score, $maxscore, $fraction, $percent, $class, $img);
|
||||
$this->itemscores[$position] = $score;
|
||||
}
|
||||
|
||||
return $this->itemscores[$position];
|
||||
}
|
||||
}
|
||||
|
@ -38,29 +38,29 @@
|
||||
min-height : 18px;
|
||||
}
|
||||
.que.ordering ul.sortablelist li.correct {
|
||||
background-color : #dff0d8; /* light green */
|
||||
background-color : #dff4d8; /* light green */
|
||||
border-color : #99ff66; /* gentle green */
|
||||
}
|
||||
.que.ordering ul.sortablelist li.partial66 {
|
||||
background-color : #dff0d8; /* light green */
|
||||
background-color : #dff4d8; /* light green */
|
||||
border-color : #ff9900; /* dark orange */
|
||||
}
|
||||
.que.ordering ul.sortablelist li.partial33 {
|
||||
background-color : #ffebcc; /* light orange */
|
||||
border-color : #ff9900; /* dark orange */
|
||||
}
|
||||
.que.ordering ul.sortablelist li.partial01 {
|
||||
background-color : #f2dede; /* light red */
|
||||
.que.ordering ul.sortablelist li.partial00 {
|
||||
background-color : #ffdddd; /* light red */
|
||||
border-color : #ff9900; /* dark orange */
|
||||
}
|
||||
.que.ordering ul.sortablelist li.incorrect {
|
||||
background-color : #f2dede; /* light red */
|
||||
background-color : #ffdddd; /* light red */
|
||||
border-color : #ff7373; /* gentle red */
|
||||
}
|
||||
.que.ordering ul.sortablelist li.correct,
|
||||
.que.ordering ul.sortablelist li.partial66,
|
||||
.que.ordering ul.sortablelist li.partial33,
|
||||
.que.ordering ul.sortablelist li.partial01,
|
||||
.que.ordering ul.sortablelist li.partial00,
|
||||
.que.ordering ul.sortablelist li.incorrect,
|
||||
.que.ordering ul.sortablelist li.unscored {
|
||||
border-style : solid;
|
||||
|
@ -31,5 +31,5 @@ $plugin->cron = 0;
|
||||
$plugin->component = 'qtype_ordering';
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
$plugin->requires = 2010112400; // Moodle 2.0
|
||||
$plugin->version = 2015123135;
|
||||
$plugin->release = '2015-12-31 (35)';
|
||||
$plugin->version = 2016010136;
|
||||
$plugin->release = '2016-01-01 (36)';
|
||||
|
Loading…
x
Reference in New Issue
Block a user