mirror of
https://github.com/moodle/moodle.git
synced 2025-08-10 11:26:41 +02:00
MDL-79873 qtype_ordering: Template to output formulation and controls.
Part of: MDL-79863
This commit is contained in:
parent
9d060922ae
commit
ec320c2e49
6 changed files with 411 additions and 346 deletions
|
@ -0,0 +1,135 @@
|
|||
<?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/>.
|
||||
|
||||
namespace qtype_ordering\output;
|
||||
|
||||
use question_attempt;
|
||||
use question_display_options;
|
||||
|
||||
/**
|
||||
* Renderable class for the displaying the formulation and controls of the question.
|
||||
*
|
||||
* @package qtype_ordering
|
||||
* @copyright 2023 Ilya Tregubov <ilya.a.tregubov@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class formulation_and_controls extends renderable_base {
|
||||
|
||||
/** @var question_display_options $options The question options. */
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* The class constructor.
|
||||
*
|
||||
* @param question_attempt $qa The question attempt object.
|
||||
*/
|
||||
public function __construct(question_attempt $qa, question_display_options $options) {
|
||||
$this->options = $options;
|
||||
parent::__construct($qa);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the data for the mustache template.
|
||||
*
|
||||
* @param \renderer_base $output renderer to be used to render the action bar elements.
|
||||
* @return array
|
||||
*/
|
||||
public function export_for_template(\renderer_base $output): array {
|
||||
global $PAGE;
|
||||
|
||||
$data = [];
|
||||
$question = $this->qa->get_question();
|
||||
|
||||
$response = $this->qa->get_last_qt_data();
|
||||
$question->update_current_response($response);
|
||||
|
||||
$currentresponse = $question->currentresponse;
|
||||
$correctresponse = $question->correctresponse;
|
||||
|
||||
// Generate fieldnames and ids.
|
||||
$responsefieldname = $question->get_response_fieldname();
|
||||
$responsename = $this->qa->get_qt_field_name($responsefieldname);
|
||||
$data['questiontext'] = $question->format_questiontext($this->qa);
|
||||
$data['ablockid'] = 'id_ablock_' . $question->id;
|
||||
$data['sortableid'] = 'id_sortable_' . $question->id;
|
||||
$data['responsename'] = $responsename;
|
||||
$data['responseid'] = 'id_' . preg_replace('/[^a-zA-Z0-9]+/', '_', $responsename);
|
||||
|
||||
// Set CSS classes for sortable list.
|
||||
if ($class = $question->get_ordering_layoutclass()) {
|
||||
$data['layoutclass'] = $class;
|
||||
}
|
||||
if ($numberingstyle = $question->options->numberingstyle) {
|
||||
$data['numberingstyle'] = $numberingstyle;
|
||||
}
|
||||
|
||||
// In the multi-tries, the highlight response base on the hint highlight option.
|
||||
if ((isset($this->options->highlightresponse) && $this->options->highlightresponse) || !$this->qa->get_state()->is_active()) {
|
||||
$data['active'] = false;
|
||||
} else if ($this->qa->get_state()->is_active()) {
|
||||
$data['active'] = true;
|
||||
}
|
||||
|
||||
$data['readonly'] = $this->options->readonly;
|
||||
|
||||
if (count($currentresponse)) {
|
||||
|
||||
// Initialize the cache for the answers' md5keys
|
||||
// this represents the initial position of the items.
|
||||
$md5keys = [];
|
||||
|
||||
// Generate ordering items.
|
||||
foreach ($currentresponse as $position => $answerid) {
|
||||
|
||||
if (!array_key_exists($answerid, $question->answers) || !array_key_exists($position, $correctresponse)) {
|
||||
continue; // Shouldn't happen !!
|
||||
}
|
||||
|
||||
// Format the answer text.
|
||||
$answer = $question->answers[$answerid];
|
||||
$answertext = $question->format_text($answer->answer, $answer->answerformat,
|
||||
$this->qa, 'question', 'answer', $answerid);
|
||||
|
||||
// The original "id" revealed the correct order of the answers
|
||||
// because $answer->fraction holds the correct order number.
|
||||
// Therefore, we use the $answer's md5key for the "id".
|
||||
$answerdata = [
|
||||
'answertext' => $answertext,
|
||||
'id' => $answer->md5key,
|
||||
];
|
||||
|
||||
if ($this->options->correctness === question_display_options::VISIBLE ||
|
||||
!empty($this->options->highlightresponse)) {
|
||||
$score = $question->get_ordering_item_score($question, $position, $answerid);
|
||||
if (isset($score['maxscore'])) {
|
||||
$renderer = $PAGE->get_renderer('qtype_ordering');
|
||||
$answerdata['feedbackimage'] = $renderer->feedback_image($score['fraction']);
|
||||
}
|
||||
$answerdata['scoreclass'] = $score['class'];
|
||||
}
|
||||
|
||||
$data['answers'][] = $answerdata;
|
||||
|
||||
// Cache this answer key.
|
||||
$md5keys[] = $answer->md5key;
|
||||
}
|
||||
}
|
||||
|
||||
$data['value'] = implode(',', $md5keys);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -74,17 +74,16 @@ class specific_grade_detail_feedback extends renderable_base {
|
|||
foreach ($currentresponse as $position => $answerid) {
|
||||
if (array_key_exists($answerid, $question->answers)) {
|
||||
$score = $question->get_ordering_item_score($question, $position, $answerid);
|
||||
[$score, $maxscore, $fraction, $percent, $class] = $score;
|
||||
if (!isset($maxscore)) {
|
||||
$score = get_string('noscore', $plugin);
|
||||
if (!isset($score['maxscore'])) {
|
||||
$score['score'] = get_string('noscore', $plugin);
|
||||
} else {
|
||||
$totalscore += $score;
|
||||
$totalmaxscore += $maxscore;
|
||||
$totalscore += $score['score'];
|
||||
$totalmaxscore += $score['maxscore'];
|
||||
}
|
||||
$data['scoredetails'][] = [
|
||||
'score' => $score,
|
||||
'maxscore' => $maxscore,
|
||||
'percent' => $percent,
|
||||
'score' => $score['score'],
|
||||
'maxscore' => $score['maxscore'],
|
||||
'percent' => $score['percent'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1065,8 +1065,14 @@ class qtype_ordering_question extends question_graded_automatically {
|
|||
};
|
||||
}
|
||||
|
||||
$score = [$score, $maxscore, $fraction, $percent, $class];
|
||||
$this->itemscores[$position] = $score;
|
||||
$itemscores = [
|
||||
'score' => $score,
|
||||
'maxscore' => $maxscore,
|
||||
'fraction' => $fraction,
|
||||
'percent' => $percent,
|
||||
'class' => $class,
|
||||
];
|
||||
$this->itemscores[$position] = $itemscores;
|
||||
}
|
||||
|
||||
return $this->itemscores[$position];
|
||||
|
|
|
@ -55,144 +55,9 @@ class qtype_ordering_renderer extends qtype_with_combined_feedback_renderer {
|
|||
* @return string HTML fragment.
|
||||
*/
|
||||
public function formulation_and_controls(question_attempt $qa, question_display_options $options) {
|
||||
global $CFG, $DB;
|
||||
|
||||
// Initialize the return result.
|
||||
$result = '';
|
||||
|
||||
$question = $qa->get_question();
|
||||
$response = $qa->get_last_qt_data();
|
||||
$question->update_current_response($response);
|
||||
|
||||
$currentresponse = $question->currentresponse;
|
||||
$correctresponse = $question->correctresponse;
|
||||
|
||||
// Generate fieldnames and ids
|
||||
// response_fieldname : 1_response_319
|
||||
// response_name : q27:1_response_319
|
||||
// response_id : id_q27_1_response_319
|
||||
// sortable_id : id_sortable_q27_1_response_319.
|
||||
$responsefieldname = $question->get_response_fieldname();
|
||||
$responsename = $qa->get_qt_field_name($responsefieldname);
|
||||
$responseid = 'id_'.preg_replace('/[^a-zA-Z0-9]+/', '_', $responsename);
|
||||
$sortableid = 'id_sortable_'.$question->id;
|
||||
$ablockid = 'id_ablock_'.$question->id;
|
||||
|
||||
// Set CSS classes for sortable list and sortable items.
|
||||
$sortablelist = 'sortablelist';
|
||||
if ($class = $question->get_ordering_layoutclass()) {
|
||||
$sortablelist .= ' '.$class; // Vertical or Horizontal.
|
||||
}
|
||||
if ($class = $question->options->numberingstyle) {
|
||||
$sortablelist .= ' numbering'.$class;
|
||||
}
|
||||
if ($qa->get_state()->is_active()) {
|
||||
$sortablelist .= ' active';
|
||||
} else {
|
||||
$sortablelist .= ' notactive';
|
||||
}
|
||||
|
||||
// In the multi-tries, the highlight response base on the hint highlight option.
|
||||
$hint = null;
|
||||
if (method_exists($qa->get_behaviour(), 'get_applicable_hint')) {
|
||||
/** @var \qtype_ordering\question_hint_ordering $hint */
|
||||
$hint = $qa->get_behaviour()->get_applicable_hint();
|
||||
}
|
||||
if ($hint && $hint->highlightresponse) {
|
||||
$sortablelist .= ' notactive';
|
||||
}
|
||||
|
||||
// Initialise JavaScript if not in readonly mode.
|
||||
if ($options->readonly) {
|
||||
// Items cannot be dragged in readonly mode.
|
||||
$sortableitem = '';
|
||||
} else {
|
||||
$sortableitem = 'sortableitem';
|
||||
$params = array($sortableid, $responseid);
|
||||
$this->page->requires->js_call_amd('qtype_ordering/drag_reorder', 'init', $params);
|
||||
}
|
||||
|
||||
$result .= html_writer::tag('div', $question->format_questiontext($qa), array('class' => 'qtext'));
|
||||
|
||||
$printeditems = false;
|
||||
if (count($currentresponse)) {
|
||||
|
||||
// Initialize the cache for the answers' md5keys
|
||||
// this represents the initial position of the items.
|
||||
$md5keys = array();
|
||||
|
||||
// Generate ordering items.
|
||||
foreach ($currentresponse as $position => $answerid) {
|
||||
|
||||
if (! array_key_exists($answerid, $question->answers)) {
|
||||
continue; // Shouldn't happen !!
|
||||
}
|
||||
if (! array_key_exists($position, $correctresponse)) {
|
||||
continue; // Shouldn't happen !!
|
||||
}
|
||||
|
||||
if ($printeditems == false) {
|
||||
$printeditems = true;
|
||||
$result .= html_writer::start_tag('div', array('class' => 'ablock', 'id' => $ablockid));
|
||||
$result .= html_writer::start_tag('div', array('class' => 'answer ordering'));
|
||||
$result .= html_writer::start_tag('ul', array('class' => $sortablelist, 'id' => $sortableid));
|
||||
}
|
||||
|
||||
// Set the CSS class and correctness img for this response.
|
||||
// (correctness: HIDDEN=0, VISIBLE=1, EDITABLE=2).
|
||||
switch ($options->correctness) {
|
||||
case question_display_options::VISIBLE:
|
||||
$score = $this->get_ordering_item_score($question, $position, $answerid);
|
||||
// To do: we need image calculation in MDL-79873.
|
||||
list($score, $maxscore, $fraction, $percent, $class, $img) = $score;
|
||||
$class = trim("$sortableitem $class");
|
||||
break;
|
||||
case question_display_options::HIDDEN:
|
||||
case question_display_options::EDITABLE:
|
||||
$class = $sortableitem;
|
||||
$img = '';
|
||||
break;
|
||||
default:
|
||||
$class = '';
|
||||
$img = '';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($hint && $hint->highlightresponse) {
|
||||
$score = $this->get_ordering_item_score($question, $position, $answerid);
|
||||
// To do: we need image calculation here in MDL-79873.
|
||||
list($score, $maxscore, $fraction, $percent, $class, $img) = $score;
|
||||
$class = trim("$sortableitem $class");
|
||||
}
|
||||
|
||||
// Format the answer text.
|
||||
$answer = $question->answers[$answerid];
|
||||
$answertext = $question->format_text($answer->answer, $answer->answerformat,
|
||||
$qa, 'question', 'answer', $answerid);
|
||||
|
||||
// The original "id" revealed the correct order of the answers
|
||||
// because $answer->fraction holds the correct order number.
|
||||
// Therefore we use the $answer's md5key for the "id".
|
||||
$params = array('class' => $class, 'id' => $answer->md5key);
|
||||
$result .= html_writer::tag('li', $img.$answertext, $params);
|
||||
|
||||
// Cache this answer key.
|
||||
$md5keys[] = $question->answers[$answerid]->md5key;
|
||||
}
|
||||
}
|
||||
|
||||
if ($printeditems) {
|
||||
$result .= html_writer::end_tag('ul');
|
||||
$result .= html_writer::end_tag('div'); // Close answer tag.
|
||||
$result .= html_writer::end_tag('div'); // Close ablock tag.
|
||||
|
||||
$result .= html_writer::empty_tag('input', array('type' => 'hidden',
|
||||
'name' => $responsename,
|
||||
'id' => $responseid,
|
||||
'value' => implode(',', $md5keys)));
|
||||
}
|
||||
|
||||
return $result;
|
||||
$formulationandcontrols = new \qtype_ordering\output\formulation_and_controls($qa, $options);
|
||||
return $this->output->render_from_template('qtype_ordering/formulation_and_controls',
|
||||
$formulationandcontrols->export_for_template($this->output));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -252,204 +117,6 @@ class qtype_ordering_renderer extends qtype_with_combined_feedback_renderer {
|
|||
|
||||
// Custom methods.
|
||||
|
||||
/**
|
||||
* Fills $this->correctinfo and $this->currentinfo depending on question options.
|
||||
* TO DO: REMOVE ME in MDL-79873
|
||||
*
|
||||
* @param object $question
|
||||
*/
|
||||
protected function get_response_info($question) {
|
||||
|
||||
$gradingtype = $question->options->gradingtype;
|
||||
switch ($gradingtype) {
|
||||
|
||||
case qtype_ordering_question::GRADING_ALL_OR_NOTHING:
|
||||
case qtype_ordering_question::GRADING_ABSOLUTE_POSITION:
|
||||
case qtype_ordering_question::GRADING_RELATIVE_TO_CORRECT:
|
||||
$this->correctinfo = $question->correctresponse;
|
||||
$this->currentinfo = $question->currentresponse;
|
||||
break;
|
||||
|
||||
case qtype_ordering_question::GRADING_RELATIVE_NEXT_EXCLUDE_LAST:
|
||||
case qtype_ordering_question::GRADING_RELATIVE_NEXT_INCLUDE_LAST:
|
||||
$lastitem = ($gradingtype == qtype_ordering_question::GRADING_RELATIVE_NEXT_INCLUDE_LAST);
|
||||
$this->correctinfo = $question->get_next_answerids($question->correctresponse, $lastitem);
|
||||
$this->currentinfo = $question->get_next_answerids($question->currentresponse, $lastitem);
|
||||
break;
|
||||
|
||||
case qtype_ordering_question::GRADING_RELATIVE_ONE_PREVIOUS_AND_NEXT:
|
||||
case qtype_ordering_question::GRADING_RELATIVE_ALL_PREVIOUS_AND_NEXT:
|
||||
$all = ($gradingtype == qtype_ordering_question::GRADING_RELATIVE_ALL_PREVIOUS_AND_NEXT);
|
||||
$this->correctinfo = $question->get_previous_and_next_answerids($question->correctresponse, $all);
|
||||
$this->currentinfo = $question->get_previous_and_next_answerids($question->currentresponse, $all);
|
||||
break;
|
||||
|
||||
case qtype_ordering_question::GRADING_LONGEST_ORDERED_SUBSET:
|
||||
case qtype_ordering_question::GRADING_LONGEST_CONTIGUOUS_SUBSET:
|
||||
$this->correctinfo = $question->correctresponse;
|
||||
$this->currentinfo = $question->currentresponse;
|
||||
$contiguous = ($gradingtype == qtype_ordering_question::GRADING_LONGEST_CONTIGUOUS_SUBSET);
|
||||
$subset = $question->get_ordered_subset($contiguous);
|
||||
foreach ($this->currentinfo as $position => $answerid) {
|
||||
if (array_search($position, $subset) === false) {
|
||||
$this->currentinfo[$position] = 0;
|
||||
} else {
|
||||
$this->currentinfo[$position] = 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns score for one item depending on correctness and question settings.
|
||||
*
|
||||
* TO DO: REMOVE ME in MDL-79873
|
||||
*
|
||||
* @param object $question
|
||||
* @param int $position
|
||||
* @param int $answerid
|
||||
* @return array (score, maxscore, fraction, percent, class, img)
|
||||
*/
|
||||
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; // Fraction $score / $maxscore.
|
||||
$percent = 0; // 100 * $fraction.
|
||||
$class = ''; // CSS class.
|
||||
$img = ''; // Icon to show correctness.
|
||||
|
||||
switch ($question->options->gradingtype) {
|
||||
|
||||
case qtype_ordering_question::GRADING_ALL_OR_NOTHING:
|
||||
if ($this->is_all_correct()) {
|
||||
$score = 1;
|
||||
$maxscore = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case qtype_ordering_question::GRADING_ABSOLUTE_POSITION:
|
||||
if (isset($correctinfo[$position])) {
|
||||
if ($correctinfo[$position] == $answerid) {
|
||||
$score = 1;
|
||||
}
|
||||
$maxscore = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case qtype_ordering_question::GRADING_RELATIVE_NEXT_EXCLUDE_LAST:
|
||||
case qtype_ordering_question::GRADING_RELATIVE_NEXT_INCLUDE_LAST:
|
||||
if (isset($correctinfo[$answerid])) {
|
||||
if (isset($currentinfo[$answerid]) && $currentinfo[$answerid] == $correctinfo[$answerid]) {
|
||||
$score = 1;
|
||||
}
|
||||
$maxscore = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case qtype_ordering_question::GRADING_RELATIVE_ONE_PREVIOUS_AND_NEXT:
|
||||
case qtype_ordering_question::GRADING_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;
|
||||
|
||||
case qtype_ordering_question::GRADING_LONGEST_ORDERED_SUBSET:
|
||||
case qtype_ordering_question::GRADING_LONGEST_CONTIGUOUS_SUBSET:
|
||||
if (isset($correctinfo[$position])) {
|
||||
if (isset($currentinfo[$position])) {
|
||||
$score = $currentinfo[$position];
|
||||
}
|
||||
$maxscore = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case qtype_ordering_question::GRADING_RELATIVE_TO_CORRECT:
|
||||
if (isset($correctinfo[$position])) {
|
||||
$maxscore = (count($correctinfo) - 1);
|
||||
$answerid = $currentinfo[$position];
|
||||
$correctposition = array_search($answerid, $correctinfo);
|
||||
$score = ($maxscore - abs($correctposition - $position));
|
||||
if ($score < 0) {
|
||||
$score = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if ($maxscore === null) {
|
||||
// An unscored item is either an illegal item
|
||||
// or last item of RELATIVE_NEXT_EXCLUDE_LAST
|
||||
// or an item in an incorrect ALL_OR_NOTHING
|
||||
// or an item from an unrecognized grading type.
|
||||
$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];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if answer is 100% correct.
|
||||
* TO DO: REMOVE ME in MDL-79873
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_all_correct() {
|
||||
if ($this->allcorrect === null) {
|
||||
// Use "==" to determine if the two "info" arrays are identical.
|
||||
$this->allcorrect = ($this->correctinfo == $this->currentinfo);
|
||||
}
|
||||
return $this->allcorrect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a brief statement of how many sub-parts of this question the
|
||||
* student got correct|partial|incorrect.
|
||||
|
@ -462,4 +129,15 @@ class qtype_ordering_renderer extends qtype_with_combined_feedback_renderer {
|
|||
return $this->output->render_from_template('qtype_ordering/num_parts_correct',
|
||||
$numpartscorrect->export_for_template($this->output));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an appropriate icon (green tick, red cross, etc.) for a grade.
|
||||
*
|
||||
* @param float $fraction grade on a scale 0..1.
|
||||
* @param bool $selected whether to show a big or small icon. (Deprecated)
|
||||
* @return string html fragment.
|
||||
*/
|
||||
public function feedback_image($fraction, $selected = true): string {
|
||||
return parent::feedback_image($fraction);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template qtype_ordering/formulation_and_controls
|
||||
|
||||
Renders the question formulation and controls.
|
||||
|
||||
Context variables required for this template:
|
||||
* questiontext - Question text.
|
||||
* responsename - Response name for this question attempt.
|
||||
* responseid - Response name for this question attempt.
|
||||
* value - Comma separated md5keys of the answer.
|
||||
* ablockid - Block id.
|
||||
* layoutclass - Layout class.
|
||||
* numberingstyle - Numbering style.
|
||||
* active - if qa is active.
|
||||
* sortableid - Sortable id.
|
||||
* answers - An array containing the score details.
|
||||
* readonly - Whether the question is readonly or not.
|
||||
Example context (json):
|
||||
{
|
||||
"questiontext": "Order this",
|
||||
"responsename": "q13:1_response_2",
|
||||
"responseid": "id_q13_1_response_2",
|
||||
"value": "ordering_item_497031794414a552435f90151ac3b54b,ordering_item_5a35edab0f2bf86dfa3901baa8c235dc",
|
||||
"ablockid": "id_ablock_2",
|
||||
"layoutclass": "vertical",
|
||||
"numberingstyle": "numberingnone",
|
||||
"active": true,
|
||||
"sortableid": "id_sortable_2",
|
||||
"readonly": true,
|
||||
"answers": [
|
||||
{
|
||||
"id": "ordering_item_497031794414a552435f90151ac3b54b",
|
||||
"scoreclass": "correct",
|
||||
"answertext": "Oriented",
|
||||
"feedbackimage": "<i class="icon fa fa-remove text-danger fa-fw" title="Incorrect" role="img" aria-label="Incorrect"></i>"
|
||||
},
|
||||
{
|
||||
"id": "ordering_item_5a35edab0f2bf86dfa3901baa8c235dc",
|
||||
"scoreclass": "correct",
|
||||
"answertext": "Object",
|
||||
"feedbackimage": "<i class="icon fa fa-remove text-danger fa-fw" title="Incorrect" role="img" aria-label="Incorrect"></i>"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
|
||||
<div class="qtext">
|
||||
{{{questiontext}}}
|
||||
{{#responsename}}
|
||||
<div class="ablock" id="{{ablockid}}">
|
||||
<div class="answer ordering">
|
||||
<ul class="sortablelist {{layoutclass}} {{#numberingstyle}}numbering{{numberingstyle}}{{/numberingstyle}} {{#active}}active{{/active}}{{^active}}notactive{{/active}}" id="{{sortableid}}">
|
||||
{{#answers}}
|
||||
<li class="{{^readonly}}sortableitem{{/readonly}} {{scoreclass}}" id="{{id}}">
|
||||
{{{feedbackimage}}}{{{answertext}}}
|
||||
</li>
|
||||
{{/answers}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<input name="{{responsename}}" id="{{responseid}}" type="hidden" value="{{value}}"/>
|
||||
{{/responsename}}
|
||||
</div>
|
||||
{{^readonly}}
|
||||
{{#js}}
|
||||
require(['qtype_ordering/drag_reorder'], function(drag_reorder) {
|
||||
drag_reorder.init('{{sortableid}}', '{{responseid}}');
|
||||
});
|
||||
{{/js}}
|
||||
{{/readonly}}
|
|
@ -0,0 +1,161 @@
|
|||
<?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/>.
|
||||
|
||||
namespace qtype_ordering\output;
|
||||
|
||||
use advanced_testcase;
|
||||
use question_display_options;
|
||||
use test_question_maker;
|
||||
use qtype_ordering_question;
|
||||
use qtype_ordering_test_helper;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
|
||||
|
||||
/**
|
||||
* A test class used to test formulation_and_controls.
|
||||
*
|
||||
* @package qtype_ordering
|
||||
* @copyright 2023 Ilya Tregubov <ilya.a.tregubov@gmail.com.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \qtype_ordering\output\specific_grade_detail_feedback
|
||||
*/
|
||||
class formulation_and_controls_test extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test the exported data for the template that renders the formulation and controls for a given question.
|
||||
*
|
||||
* @dataProvider export_for_template_provider
|
||||
* @param array $answeritems The array of ordered answers.
|
||||
* @param int $gradingtype Grading type.
|
||||
* @param string $layouttype The type of the layout.
|
||||
* @param array $expected The expected exported data.
|
||||
* @return void
|
||||
* @covers ::export_for_template
|
||||
*/
|
||||
public function test_export_for_template(array $answeritems, int $gradingtype, string $layouttype, array $expected): void {
|
||||
global $PAGE;
|
||||
|
||||
$question = test_question_maker::make_question('ordering');
|
||||
$question->options->layouttype = $layouttype === 'horizontal' ? qtype_ordering_question::LAYOUT_HORIZONTAL :
|
||||
qtype_ordering_question::LAYOUT_VERTICAL;
|
||||
$qa = new \testable_question_attempt($question, 0);
|
||||
$step = new \question_attempt_step();
|
||||
$qa->add_step($step);
|
||||
$question->start_attempt($step, 1);
|
||||
|
||||
$options = new question_display_options();
|
||||
$options->feedback = question_display_options::VISIBLE;
|
||||
$options->numpartscorrect = question_display_options::VISIBLE;
|
||||
$options->generalfeedback = question_display_options::VISIBLE;
|
||||
$options->rightanswer = question_display_options::VISIBLE;
|
||||
$options->manualcomment = question_display_options::VISIBLE;
|
||||
$options->history = question_display_options::VISIBLE;
|
||||
$question->options->gradingtype = $gradingtype;
|
||||
|
||||
$keys = implode(',', array_keys($answeritems));
|
||||
$values = array_values($answeritems);
|
||||
|
||||
$step->set_qt_var('_currentresponse', $keys);
|
||||
|
||||
[$fraction, $state] = $question->grade_response(qtype_ordering_test_helper::get_response($question, $values));
|
||||
$qa->get_last_step()->set_state($state);
|
||||
|
||||
$renderer = $PAGE->get_renderer('core');
|
||||
$formulationandcontrols = new formulation_and_controls($qa, $options);
|
||||
$actual = $formulationandcontrols->export_for_template($renderer);
|
||||
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for the test_export_for_template test.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function export_for_template_provider(): array {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/question/type/ordering/question.php');
|
||||
$success = "<i class=\"icon fa fa-check text-success fa-fw \" title=\"Correct\" role=\"img\" aria-label=\"Correct\"></i>";
|
||||
$warning = "<i class=\"icon fa fa-check-square fa-fw \" title=\"Partially correct\" role=\"img\" aria-label=\"Partially correct\"></i>";
|
||||
$error = "<i class=\"icon fa fa-remove text-danger fa-fw \" title=\"Incorrect\" role=\"img\" aria-label=\"Incorrect\"></i>";
|
||||
|
||||
return [
|
||||
'Horizontal, correct and partially correct' => [
|
||||
[13 => 'Modular', 14 => 'Object', 15 => 'Oriented', 17 => 'Learning', 16 => 'Dynamic', 18 => 'Environment'],
|
||||
qtype_ordering_question::GRADING_RELATIVE_ALL_PREVIOUS_AND_NEXT,
|
||||
'horizontal',
|
||||
[
|
||||
'readonly' => false,
|
||||
'questiontext' => 'Put these words in order',
|
||||
'responsename' => 'q0:_response_0',
|
||||
'responseid' => 'id_q0_response_0',
|
||||
'value' => 'ordering_item_ac5fc041de63c8c5b34d0aabb96cf33d,' .
|
||||
'ordering_item_497031794414a552435f90151ac3b54b,' .
|
||||
'ordering_item_5a35edab0f2bf86dfa3901baa8c235dc,' .
|
||||
'ordering_item_8af0f5c3edad8d8e158ff27b9f03afac,' .
|
||||
'ordering_item_971fd8cc345d8bd9f92e9f7d88fdf20c,' .
|
||||
'ordering_item_0ba29c6a1afacf586b03a26162c72274',
|
||||
'ablockid' => 'id_ablock_0',
|
||||
'layoutclass' => 'horizontal',
|
||||
'numberingstyle' => 'none',
|
||||
'active' => false,
|
||||
'sortableid' => 'id_sortable_0',
|
||||
'answers' => [
|
||||
['scoreclass' => 'correct', 'id' => 'ordering_item_' . md5('Modular'), 'answertext' => "Modular", 'feedbackimage' => $success],
|
||||
['scoreclass' => 'correct', 'id' => 'ordering_item_' . md5('Object'), 'answertext' => "Object", 'feedbackimage' => $success],
|
||||
['scoreclass' => 'correct', 'id' => 'ordering_item_' . md5('Oriented'), 'answertext' => "Oriented", 'feedbackimage' => $success],
|
||||
['scoreclass' => 'partial66', 'id' => 'ordering_item_' . md5('Learning'), 'answertext' => "Learning", 'feedbackimage' => $warning],
|
||||
['scoreclass' => 'partial66', 'id' => 'ordering_item_' . md5('Dynamic'), 'answertext' => "Dynamic", 'feedbackimage' => $warning],
|
||||
['scoreclass' => 'correct', 'id' => 'ordering_item_' . md5('Environment'), 'answertext' => "Environment", 'feedbackimage' => $success],
|
||||
],
|
||||
],
|
||||
],
|
||||
'Vertical, incorrect' => [
|
||||
[14 => 'Object', 16 => 'Dynamic', 13 => 'Modular', 17 => 'Learning', 18 => 'Environment', 15 => 'Oriented'],
|
||||
qtype_ordering_question::GRADING_ABSOLUTE_POSITION,
|
||||
'vertical',
|
||||
[
|
||||
'readonly' => false,
|
||||
'questiontext' => 'Put these words in order',
|
||||
'responsename' => 'q0:_response_0',
|
||||
'responseid' => 'id_q0_response_0',
|
||||
'value' => 'ordering_item_497031794414a552435f90151ac3b54b,' .
|
||||
'ordering_item_971fd8cc345d8bd9f92e9f7d88fdf20c,' .
|
||||
'ordering_item_ac5fc041de63c8c5b34d0aabb96cf33d,' .
|
||||
'ordering_item_8af0f5c3edad8d8e158ff27b9f03afac,' .
|
||||
'ordering_item_0ba29c6a1afacf586b03a26162c72274,' .
|
||||
'ordering_item_5a35edab0f2bf86dfa3901baa8c235dc',
|
||||
'ablockid' => 'id_ablock_0',
|
||||
'layoutclass' => 'vertical',
|
||||
'numberingstyle' => 'none',
|
||||
'active' => false,
|
||||
'sortableid' => 'id_sortable_0',
|
||||
'answers' => [
|
||||
['scoreclass' => 'incorrect', 'id' => 'ordering_item_' . md5('Object'), 'answertext' => "Object", 'feedbackimage' => $error],
|
||||
['scoreclass' => 'incorrect', 'id' => 'ordering_item_' . md5('Dynamic'), 'answertext' => "Dynamic", 'feedbackimage' => $error],
|
||||
['scoreclass' => 'incorrect', 'id' => 'ordering_item_' . md5('Modular'), 'answertext' => "Modular", 'feedbackimage' => $error],
|
||||
['scoreclass' => 'incorrect', 'id' => 'ordering_item_' . md5('Learning'), 'answertext' => "Learning", 'feedbackimage' => $error],
|
||||
['scoreclass' => 'incorrect', 'id' => 'ordering_item_' . md5('Environment'), 'answertext' => "Environment", 'feedbackimage' => $error],
|
||||
['scoreclass' => 'incorrect', 'id' => 'ordering_item_' . md5('Oriented'), 'answertext' => "Oriented", 'feedbackimage' => $error],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue