MDL-20636 Re-organise all the old upgrade code, before I start work on it for real.

This commit is contained in:
Tim Hunt
2011-05-11 15:32:14 +01:00
parent 0de4393945
commit 0859ff6503
11 changed files with 1841 additions and 1970 deletions
@@ -0,0 +1,509 @@
<?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 classes for handling the different question behaviours
* during upgrade.
*
* @package moodlecore
* @subpackage questionengine
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
abstract class question_behaviour_attempt_updater {
protected $qtypeupdater;
protected $logger;
protected $qa;
protected $quiz;
protected $attempt;
protected $question;
protected $qsession;
protected $qstates;
protected $sequencenumber;
protected $finishstate;
protected $alreadystarted;
public function __construct($quiz, $attempt, $question, $qsession, $qstates, $logger) {
$this->quiz = $quiz;
$this->attempt = $attempt;
$this->question = $question;
$this->qsession = $qsession;
$this->qstates = $qstates;
$this->logger = $logger;
}
public function discard() {
// Help the garbage collector, which seems to be struggling.
$this->quiz = null;
$this->attempt = null;
$this->question = null;
$this->qsession = null;
$this->qstates = null;
$this->qa = null;
$this->qtypeupdater->discard();
$this->qtypeupdater = null;
$this->logger = null;
}
protected abstract function behaviour_name();
public function get_converted_qa() {
$this->initialise_qa();
$this->convert_steps();
return $this->qa;
}
protected function create_missing_first_step() {
$step = new stdClass();
$step->state = 'todo';
$step->data = array();
$step->fraction = null;
$step->timecreated = $this->attempt->timestart;
$step->userid = $this->attempt->userid;
$this->qtypeupdater->supply_missing_first_step_data($step->data);
return $step;
}
public function supply_missing_qa() {
$this->initialise_qa();
$this->qa->timemodified = $this->attempt->timestart;
$this->sequencenumber = 0;
$this->add_step($this->create_missing_first_step());
return $this->qa;
}
protected function initialise_qa() {
$this->qtypeupdater = $this->make_qtype_updater();
$qa = new stdClass();
$qa->questionid = $this->question->id;
$qa->behaviour = $this->behaviour_name();
$qa->maxmark = $this->question->maxmark;
$qa->minfraction = 0;
$qa->flagged = 0;
$qa->questionsummary = $this->qtypeupdater->question_summary($this->question);
$qa->rightanswer = $this->qtypeupdater->right_answer($this->question);
$qa->responsesummary = '';
$qa->timemodified = 0;
$qa->steps = array();
$this->qa = $qa;
}
protected function convert_steps() {
$this->finishstate = null;
$this->startstate = null;
$this->sequencenumber = 0;
foreach ($this->qstates as $state) {
$this->process_state($state);
}
$this->finish_up();
}
protected function process_state($state) {
$step = $this->make_step($state);
$method = 'process' . $state->event;
$this->$method($step, $state);
}
protected function finish_up() {
}
protected function add_step($step) {
$step->sequencenumber = $this->sequencenumber;
$this->qa->steps[] = $step;
$this->sequencenumber++;
}
protected function discard_last_state() {
array_pop($this->qa->steps);
$this->sequencenumber--;
}
protected function unexpected_event($state) {
throw new coding_exception("Unexpected event {$state->event} in state {$state->id} in question session {$this->qsession->id}.");
}
protected function process0($step, $state) {
if ($this->startstate) {
if ($state->answer == reset($this->qstates)->answer) {
return;
} else if ($this->quiz->attemptonlast && $this->sequencenumber == 1) {
// There was a bug in attemptonlast in the past, which meant that
// it created two inconsistent open states, with the second taking
// priority. Simulate that be discarding the first open state, then
// continuing.
$this->logger->log_assumption("Ignoring bogus state in attempt at question {$state->question}");
$this->sequencenumber = 0;
$this->qa->steps = array();
} else if ($state->answer == '') {
$this->logger->log_assumption("Ignoring second start state with blank answer in attempt at question {$state->question}");
return;
} else {
throw new coding_exception("Two inconsistent open states for question session {$this->qsession->id}.");
}
}
$step->state = 'todo';
$this->startstate = $state;
$this->add_step($step);
}
protected function process1($step, $state) {
$this->unexpected_event($state);
}
protected function process2($step, $state) {
if ($this->qtypeupdater->was_answered($state)) {
$step->state = 'complete';
} else {
$step->state = 'todo';
}
$this->add_step($step);
}
protected function process3($step, $state) {
return $this->process6($step, $state);
}
protected function process4($step, $state) {
$this->unexpected_event($state);
}
protected function process5($step, $state) {
$this->unexpected_event($state);
}
protected abstract function process6($step, $state);
protected abstract function process7($step, $state);
protected function process8($step, $state) {
return $this->process6($step, $state);
}
protected function process9($step, $state) {
if (!$this->finishstate) {
$submitstate = clone($state);
$submitstate->event = 8;
$submitstate->grade = 0;
$this->process_state($submitstate);
}
$step->data['-comment'] = $this->qsession->manualcomment;
if ($this->question->maxmark > 0) {
$step->fraction = $state->grade / $this->question->maxmark;
$step->state = $this->manual_graded_state_for_fraction($step->fraction);
$step->data['-mark'] = $state->grade;
$step->data['-maxmark'] = $this->question->maxmark;
} else {
$step->state = 'manfinished';
}
unset($step->data['answer']);
$step->userid = null;
$this->add_step($step);
}
protected function process10($step, $state) {
$this->unexpected_event($state);
}
/**
* @param object $question a question definition
* @return qtype_updater
*/
protected function make_qtype_updater() {
$class = 'qtype_' . $this->question->qtype . '_updater';
return new $class($this, $this->question, $this->logger);
}
public function to_text($html) {
return trim(html_to_text($html, 0, false));
}
protected function graded_state_for_fraction($fraction) {
if ($fraction < 0.000001) {
return 'gradedwrong';
} else if ($fraction > 0.999999) {
return 'gradedright';
} else {
return 'gradedpartial';
}
}
protected function manual_graded_state_for_fraction($fraction) {
if ($fraction < 0.000001) {
return 'mangrwrong';
} else if ($fraction > 0.999999) {
return 'mangrright';
} else {
return 'mangrpartial';
}
}
protected function make_step($state){
$step = new stdClass();
$step->data = array();
if ($state->event == 0 || $this->sequencenumber == 0) {
$this->qtypeupdater->set_first_step_data_elements($state, $step->data);
} else {
$this->qtypeupdater->set_data_elements_for_step($state, $step->data);
}
$step->fraction = null;
$step->timecreated = $state->timestamp;
$step->userid = $this->attempt->userid;
$summary = $this->qtypeupdater->response_summary($state);
if (!is_null($summary)) {
$this->qa->responsesummary = $summary;
}
$this->qa->timemodified = max($this->qa->timemodified, $state->timestamp);
return $step;
}
}
class qbehaviour_deferredfeedback_converter extends question_behaviour_attempt_updater {
protected function behaviour_name() {
return 'deferredfeedback';
}
protected function process6($step, $state) {
if (!$this->startstate) {
$this->logger->log_assumption("Ignoring bogus submit before open in attempt at question {$state->question}");
// WTF, but this has happened a few times in our DB. It seems it is safe to ignore.
return;
}
if ($this->finishstate) {
if ($this->finishstate->answer != $state->answer ||
$this->finishstate->grade != $state->grade ||
$this->finishstate->raw_grade != $state->raw_grade ||
$this->finishstate->penalty != $state->penalty) {
$this->logger->log_assumption("Two inconsistent finish states found for question session {$this->qsession->id} in attempt at question {$state->question} keeping the later one.");
$this->discard_last_state();
} else {
$this->logger->log_assumption("Ignoring extra finish states in attempt at question {$state->question}");
return;
}
}
if ($this->question->maxmark > 0) {
$step->fraction = $state->grade / $this->question->maxmark;
$step->state = $this->graded_state_for_fraction($step->fraction);
} else {
$step->state = 'finished';
}
$step->data['-finish'] = '1';
$this->finishstate = $state;
$this->add_step($step);
}
protected function process7($step, $state) {
$this->unexpected_event($state);
}
}
class qbehaviour_manualgraded_converter extends question_behaviour_attempt_updater {
protected function behaviour_name() {
return 'manualgraded';
}
protected function process6($step, $state) {
$step->state = 'needsgrading';
if (!$this->finishstate) {
$step->data['-finish'] = '1';
$this->finishstate = $state;
}
$this->add_step($step);
}
protected function process7($step, $state) {
return $this->process6($step, $state);
}
}
class qbehaviour_informationitem_converter extends question_behaviour_attempt_updater {
protected function behaviour_name() {
return 'informationitem';
}
protected function process0($step, $state) {
if ($this->startstate) {
return;
}
$step->state = 'todo';
$this->startstate = $state;
$this->add_step($step);
}
protected function process2($step, $state) {
$this->unexpected_event($state);
}
protected function process3($step, $state) {
$this->unexpected_event($state);
}
protected function process6($step, $state) {
if ($this->finishstate) {
return;
}
$step->state = 'finished';
$step->data['-finish'] = '1';
$this->finishstate = $state;
$this->add_step($step);
}
protected function process7($step, $state) {
return $this->process6($step, $state);
}
protected function process8($step, $state) {
return $this->process6($step, $state);
}
}
class qbehaviour_interactive_converter extends question_behaviour_attempt_updater {
protected $triesleft;
protected function behaviour_name() {
return 'interactive';
}
protected function finish_up() {
if ($this->triesleft == 0 || !$this->attempt->timefinish) {
return;
}
$state = end($this->qstates);
$step = $this->make_step($state);
$step->data['-finish'] = 1;
if ($this->question->maxmark > 0) {
$step->fraction = $state->grade / $this->question->maxmark;
$step->state = $this->graded_state_for_fraction($step->fraction);
} else {
$step->state = 'finished';
}
$this->add_step($step);
}
protected function process0($step, $state) {
$this->triesleft = 1;
if (!empty($this->question->hints)) {
$this->triesleft += count($this->question->hints);
}
$step->data['-_triesleft'] = $this->triesleft;
parent::process0($step, $state);
}
protected function process2($step, $state) {
if ($this->finishstate) {
$this->logger->log_assumption("Ignoring bogus save after submit, and before try again, in interactive attempt at question {$state->question} (question session {$this->qsession->id})");
return;
}
parent::process2($step, $state);
}
protected function process3($step, $state) {
if ($state->id == $this->qsession->newgraded) {
return $this->process6($step, $state);
} else {
return;
}
}
protected function process6($step, $state) {
if ($this->finishstate) {
if (!$this->qtypeupdater->compare_answers($this->finishstate->answer, $state->answer) ||
$this->finishstate->grade != $state->grade ||
$this->finishstate->raw_grade != $state->raw_grade ||
$this->finishstate->penalty != $state->penalty) {
throw new coding_exception("Two inconsistent finish states found for question session {$this->qsession->id}.");
} else if ($this->triesleft) {
$step->data = array('-finish' => '1');
if ($this->question->maxmark > 0) {
$step->fraction = $state->grade / $this->question->maxmark;
$step->state = $this->graded_state_for_fraction($step->fraction);
} else {
$step->state = 'finished';
}
$this->finishstate = $state;
$this->add_step($step);
$this->triesleft = 0;
return;
} else {
$this->logger->log_assumption("Ignoring extra finish states in attempt at question {$state->question}");
return;
}
}
if ($this->question->maxmark > 0) {
$step->fraction = $state->grade / $this->question->maxmark;
$step->state = $this->graded_state_for_fraction($state->raw_grade / $this->question->maxmark);
} else {
$step->state = 'finished';
}
$this->triesleft--;
$step->data['-submit'] = '1';
if ($this->triesleft && $step->state != 'gradedright') {
$step->state = 'todo';
$step->fraction = null;
$step->data['-_triesleft'] = $this->triesleft;
} else {
$this->triesleft = 0;
}
$this->finishstate = $state;
$this->add_step($step);
}
protected function process7($step, $state) {
$this->unexpected_event($state);
}
protected function process10($step, $state) {
if (!$this->finishstate) {
$oldcount = $this->sequencenumber;
$this->process6($step, $state);
if ($this->sequencenumber != $oldcount + 1) {
throw new coding_exception('Submit before try again did not keep the step.');
}
$step = $this->make_step($state);
}
$step->state = 'todo';
$step->data = array('-tryagain' => 1);
$this->finishstate = null;
$this->add_step($step);
}
}
+77
View File
@@ -0,0 +1,77 @@
<?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/>.
/**
* Code that deals with logging stuff during the question engine upgrade.
*
* @package moodlecore
* @subpackage questionengine
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/questionlib.php');
/**
* This class serves to record all the assumptions that the code had to make
* during the question engine database database upgrade, to facilitate reviewing
* them.
*
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class question_engine_assumption_logger {
protected $handle;
protected $attemptid;
public function __construct() {
global $CFG;
make_upload_directory('upgradelogs');
$this->handle = fopen($CFG->dataroot . '/upgradelogs/qe_' .
date('Ymd-Hi') . '.html', 'a');
fwrite($this->handle, '<html><head><title>Question engine upgrade assumptions ' .
date('Ymd-Hi') . '</title></head><body><h2>Question engine upgrade assumptions ' .
date('Ymd-Hi') . "</h2>\n\n");
}
public function set_current_attempt_id($id) {
$this->attemptid = $id;
}
public function log_assumption($description, $quizattemptid = null) {
global $CFG;
$message = '<p>' . $description;
if (!$quizattemptid) {
$quizattemptid = $this->attemptid;
}
if ($quizattemptid) {
$message .= ' (<a href="' . $CFG->wwwroot . '/mod/quiz/review.php?attempt=' .
$quizattemptid . '">Review this attempt</a>)';
}
$message .= "</p>\n";
fwrite($this->handle, $message);
}
public function __destruct() {
fwrite($this->handle, '</body></html>');
fclose($this->handle);
}
}
+561
View File
@@ -0,0 +1,561 @@
<?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 the code required to upgrade all the attempt data from
* old versions of Moodle into the tables used by the new question engine.
*
* @package moodlecore
* @subpackage questionengine
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/question/engine/upgrade/logger.php');
require_once($CFG->dirroot . '/question/engine/upgrade/behaviourconverters.php');
/**
* This class manages upgrading all the question attempts from the old database
* structure to the new question engine.
*
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class question_engine_attempt_upgrader {
/** @var question_engine_upgrade_question_loader */
protected $questionloader;
protected $logger;
protected function print_progress($done, $outof, $quizid) {
gc_collect_cycles();
print_progress($done, $outof);
}
protected function prevent_timeout() {
set_time_limit(300);
}
protected function table_exists($tablename) {
global $db;
$metatables = $db->MetaTables();
$metatables = array_flip($metatables);
$metatables = array_change_key_case($metatables, CASE_LOWER);
return array_key_exists($tablename, $metatables);
}
protected function get_quiz_ids() {
global $CFG;
if ($this->table_exists('vl_v_crs_version_pres')) {
$quizmoduleid = get_field('modules', 'id', 'name', 'quiz');
return get_records_sql_menu("
SELECT quiz.id,1
FROM {$CFG->prefix}quiz quiz
JOIN {$CFG->prefix}course_modules cm ON cm.module = {$quizmoduleid}
AND cm.instance = quiz.id
JOIN {$CFG->prefix}course c ON quiz.course = c.id
LEFT JOIN vl_v_crs_version_pres v ON v.vle_course_short_name = c.shortname
AND v.vle_control_course = 'Y'
WHERE cm.idnumber <> ''
OR v.vle_student_close_date IS NULL
OR (v.vle_student_close_date > '2010-12-01' AND c.shortname <> 'MU123-10B')
");
} else {
return get_records_menu('quiz', '', '', 'id', 'id,1');
}
}
public function convert_all_quiz_attempts() {
$quizids = $this->get_quiz_ids();
if (!$quizids) {
return true;
}
$done = 0;
$outof = count($quizids);
$this->logger = new question_engine_assumption_logger();
$success = true;
foreach ($quizids as $quizid => $notused) {
$this->print_progress($done, $outof, $quizid);
$quiz = get_record('quiz', 'id', $quizid);
$success = $success && $this->update_all_attemtps_at_quiz($quiz);
if (!$success) {
return false;
}
$done += 1;
}
$this->print_progress($outof, $outof, 'All done!');
$this->logger = null;
return $success;
}
public function get_attemtps_where($quizid) {
return "quiz = {$quizid} AND preview = 0 AND needsupgradetonewqe = 1";
}
public function update_all_attemtps_at_quiz($quiz) {
global $CFG;
// Wipe question loader cache.
$this->questionloader = new question_engine_upgrade_question_loader($this->logger);
begin_sql();
$where = $this->get_attemtps_where($quiz->id);
$quizattemptsrs = get_recordset_select('quiz_attempts', $where, 'uniqueid');
$questionsessionsrs = get_recordset_sql("
SELECT *
FROM {$CFG->prefix}question_sessions
WHERE attemptid IN (
SELECT uniqueid FROM {$CFG->prefix}quiz_attempts WHERE $where)
ORDER BY attemptid, questionid
");
$questionsstatesrs = get_recordset_sql("
SELECT *
FROM {$CFG->prefix}question_states
WHERE attempt IN (
SELECT uniqueid FROM {$CFG->prefix}quiz_attempts WHERE $where)
ORDER BY attempt, question, seq_number, id
");
$success = $quizattemptsrs && $questionsessionsrs && $questionsstatesrs;
while ($success && ($attempt = rs_fetch_next_record($quizattemptsrs))) {
$success = $success && $this->convert_quiz_attempt(
$quiz, $attempt, $questionsessionsrs, $questionsstatesrs);
}
rs_close($quizattemptsrs);
rs_close($questionsessionsrs);
rs_close($questionsstatesrs);
if ($success) {
commit_sql();
} else {
rollback_sql();
}
return $success;
}
protected function convert_quiz_attempt($quiz, $attempt, $questionsessionsrs, $questionsstatesrs) {
$qas = array();
$this->logger->set_current_attempt_id($attempt->id);
while ($qsession = $this->get_next_question_session($attempt, $questionsessionsrs)) {
$question = $this->load_question($qsession->questionid, $quiz->id);
$qstates = $this->get_question_states($attempt, $question, $questionsstatesrs);
try {
$qas[$qsession->questionid] = $this->convert_question_attempt($quiz, $attempt, $question, $qsession, $qstates);
} catch (Exception $e) {
notify($e->getMessage());
}
}
$this->logger->set_current_attempt_id(null);
if (empty($qas)) {
$this->logger->log_assumption("All the question attempts for
attempt {$attempt->id} at quiz {$attempt->quiz} were missing.
Deleting this attempt", $attempt->id);
// Somehow, all the question attempt data for this quiz attempt
// was lost. (This seems to have happened on labspace.)
// Delete the corresponding quiz attempt.
return $this->delete_quiz_attempt($attempt->uniqueid);
}
$questionorder = array();
foreach (explode(',', $quiz->questions) as $questionid) {
if ($questionid == 0) {
continue;
}
if (!array_key_exists($questionid, $qas)) {
$this->logger->log_assumption("Supplying minimal open state for
question {$questionid} in attempt {$attempt->id} at quiz
{$attempt->quiz}, since the session was missing.", $attempt->id);
try {
$qas[$questionid] = $this->supply_missing_question_attempt(
$quiz, $attempt, $question);
} catch (Exception $e) {
notify($e->getMessage());
}
}
}
return $this->save_usage($quiz->preferredbehaviour, $attempt, $qas, $quiz->questions);
}
protected function save_usage($preferredbehaviour, $attempt, $qas, $quizlayout) {
$missing = array();
$success = true;
$layout = explode(',', $attempt->layout);
$questionkeys = array_combine(array_values($layout), array_keys($layout));
$success = $success && $this->set_quba_preferred_behaviour($attempt->uniqueid, $preferredbehaviour);
if (!$success) {
return false;
}
$i = 0;
foreach (explode(',', $quizlayout) as $questionid) {
if ($questionid == 0) {
continue;
}
$i++;
if (!array_key_exists($questionid, $qas)) {
$missing[] = $questionid;
continue;
}
$qa = $qas[$questionid];
$qa->questionusageid = $attempt->uniqueid;
$qa->slot = $i;
$success = $success && $this->insert_record('question_attempts', $qa);
if (!$success) {
return false;
}
$layout[$questionkeys[$questionid]] = $qa->slot;
foreach ($qa->steps as $step) {
$step->questionattemptid = $qa->id;
$success = $success && $this->insert_record('question_attempt_steps', $step);
if (!$success) {
return false;
}
foreach ($step->data as $name => $value) {
$datum = new stdClass();
$datum->attemptstepid = $step->id;
$datum->name = $name;
$datum->value = $value;
$success = $success && $this->insert_record(
'question_attempt_step_data', $datum, false);
}
}
}
$success = $success && $this->set_quiz_attempt_layout($attempt->uniqueid, implode(',', $layout));
if ($missing) {
notify("Question sessions for questions " .
implode(', ', $missing) .
" were missing when upgrading question usage {$attempt->uniqueid}.");
}
return $success;
}
protected function set_quba_preferred_behaviour($qubaid, $preferredbehaviour) {
return set_field('question_usages', 'preferredbehaviour', $preferredbehaviour, 'id', $qubaid);
}
protected function set_quiz_attempt_layout($qubaid, $layout) {
$success = true;
$success = $success && set_field('quiz_attempts', 'layout', $layout, 'uniqueid', $qubaid);
$success = $success && set_field('quiz_attempts', 'needsupgradetonewqe', 0, 'uniqueid', $qubaid);
return $success;
}
protected function delete_quiz_attempt($qubaid) {
$success = true;
$success = $success && delete_records('quiz_attempts', 'uniqueid', $qubaid);
$success = $success && delete_records('question_attempts', 'id', $qubaid);
return $success;
}
protected function escape_fields($record) {
foreach (get_object_vars($record) as $field => $value) {
if (is_string($value)) {
$record->$field = $value;
}
}
}
protected function insert_record($table, $record, $saveid = true) {
$this->escape_fields($record);
$newid = insert_record($table, $record, $saveid);
if ($saveid) {
$record->id = $newid;
}
return $newid;
}
public function load_question($questionid, $quizid = null) {
return $this->questionloader->get_question($questionid, $quizid);
}
public function get_next_question_session($attempt, $questionsessionsrs) {
$qsession = rs_fetch_record($questionsessionsrs);
if (!$qsession || $qsession->attemptid != $attempt->uniqueid) {
// No more question sessions belonging to this attempt.
return false;
}
// Session found, move the pointer in the RS and return the record.
rs_next_record($questionsessionsrs);
return $qsession;
}
public function get_question_states($attempt, $question, $questionsstatesrs) {
$qstates = array();
while ($state = rs_fetch_record($questionsstatesrs)) {
if (!$state || $state->attempt != $attempt->uniqueid ||
$state->question != $question->id) {
// We have found all the states for this attempt. Stop.
break;
}
// Add the new state to the array, and advance.
$qstates[$state->seq_number] = $state;
rs_next_record($questionsstatesrs);
}
return $qstates;
}
protected function get_converter_class_name($question, $quiz, $qsessionid) {
if (in_array($question->qtype, array('calculated', 'multianswer', 'randomsamatch'))) {
throw new coding_exception("Question session {$qsessionid} uses unsupported question type {$question->qtype}.");
} else if ($question->qtype == 'essay') {
return 'qbehaviour_manualgraded_converter';
} else if ($question->qtype == 'description') {
return 'qbehaviour_informationitem_converter';
} else if ($question->qtype == 'opaque') {
return 'qbehaviour_opaque_converter';
} else if ($quiz->preferredbehaviour == 'interactive') {
return 'qbehaviour_interactive_converter';
} else if ($quiz->preferredbehaviour == 'deferredfeedback') {
return 'qbehaviour_deferredfeedback_converter';
} else {
throw new coding_exception("Question session {$qsessionid} has an unexpected preferred behaviour {$quiz->preferredbehaviour}.");
}
}
public function supply_missing_question_attempt($quiz, $attempt, $question) {
if ($question->qtype == 'random') {
throw new coding_exception("Cannot supply a missing qsession for question {$question->id} in attempt {$attempt->id}.");
}
$converterclass = $this->get_converter_class_name($question, $quiz, 'missing');
$qbehaviourupdater = new $converterclass($quiz, $attempt, $question, null, null, $this->logger);
$qa = $qbehaviourupdater->supply_missing_qa();
$qbehaviourupdater->discard();
return $qa;
}
public function convert_question_attempt($quiz, $attempt, $question, $qsession, $qstates) {
$this->prevent_timeout();
if ($question->qtype == 'random') {
list($question, $qstates) = $this->decode_random_attempt($qstates, $question->maxmark);
$qsession->questionid = $question->id;
}
$converterclass = $this->get_converter_class_name($question, $quiz, $qsession->id);
$qbehaviourupdater = new $converterclass($quiz, $attempt, $question, $qsession, $qstates, $this->logger);
$qa = $qbehaviourupdater->get_converted_qa();
$qbehaviourupdater->discard();
return $qa;
}
protected function decode_random_attempt($qstates, $maxmark) {
$realquestionid = null;
foreach ($qstates as $i => $state) {
if (strpos($state->answer, '-') < 6) {
// Broken state, skip it.
$this->logger->log_assumption("Had to skip brokes state {$state->id}
for question {$state->question}.");
unset($qstates[$i]);
continue;
}
list($randombit, $realanswer) = explode('-', $state->answer, 2);
$newquestionid = substr($randombit, 6);
if ($realquestionid && $realquestionid != $newquestionid) {
throw new coding_exception("Question session {$this->qsession->id} for random question points to two different real questions {$realquestionid} and {$newquestionid}.");
}
$qstates[$i]->answer = $realanswer;
}
if (empty($newquestionid)) {
// This attempt only had broken states. Set a fake $newquestionid to
// prevent a null DB error later.
$newquestionid = 0;
}
$newquestion = $this->load_question($newquestionid);
$newquestion->maxmark = $maxmark;
return array($newquestion, $qstates);
}
}
/**
* This class deals with loading (and caching) question definitions during the
* question engine upgrade.
*
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class question_engine_upgrade_question_loader {
private $cache = array();
public function __construct($logger) {
$this->logger = $logger;
}
protected function load_question($questionid, $quizid) {
global $CFG;
if ($quizid) {
$question = get_record_sql("
SELECT q.*, qqi.grade AS maxmark
FROM {$CFG->prefix}question q
JOIN {$CFG->prefix}quiz_question_instances qqi ON qqi.question = q.id
WHERE q.id = $questionid AND qqi.quiz = $quizid");
} else {
$question = get_record('question', 'id', $questionid);
}
if (!$question) {
return null;
}
if (empty($question->defaultmark)) {
if (!empty($question->defaultgrade)) {
$question->defaultmark = $question->defaultgrade;
} else {
$question->defaultmark = 0;
}
unset($question->defaultgrade);
}
$qtype = question_bank::get_qtype($question->qtype, false);
if ($qtype->name() === 'missingtype') {
$this->logger->log_assumption("Dealing with question id {$question->id}
that is of an unknown type {$question->qtype}.");
$question->questiontext = '<p>' . get_string('warningmissingtype', 'quiz') . '</p>' . $question->questiontext;
}
$qtype->get_question_options($question);
return $question;
}
public function get_question($questionid, $quizid) {
if (isset($this->cache[$questionid])) {
return $this->cache[$questionid];
}
$question = $this->load_question($questionid, $quizid);
if (!$question) {
$this->logger->log_assumption("Dealing with question id {$questionid}
that was missing from the database.");
$question = new stdClass();
$question->id = $questionid;
$question->qtype = 'deleted';
$question->maxmark = 1; // Guess, but that is all we can do.
$question->questiontext = get_string('deletedquestiontext', 'qtype_missingtype');
}
$this->cache[$questionid] = $question;
return $this->cache[$questionid];
}
}
abstract class question_qtype_attempt_updater {
/** @var question_engine_attempt_upgrader */
protected $question;
protected $updater;
/** @var question_engine_assumption_logger */
protected $logger;
public function __construct($updater, $question, $logger) {
$this->updater = $updater;
$this->question = $question;
$this->logger = $logger;
}
public function discard() {
// Help the garbage collector, which seems to be struggling.
$this->updater = null;
$this->question = null;
$this->logger = null;
}
protected function to_text($html) {
return $this->updater->to_text($html);
}
public function question_summary() {
return $this->to_text($this->question->questiontext);
}
public function compare_answers($answer1, $answer2) {
return $answer1 == $answer2;
}
public abstract function right_answer();
public abstract function response_summary($state);
public abstract function was_answered($state);
public abstract function set_first_step_data_elements($state, &$data);
public abstract function set_data_elements_for_step($state, &$data);
public abstract function supply_missing_first_step_data(&$data);
}
class question_deleted_question_attempt_updater extends question_qtype_attempt_updater {
public function right_answer() {
return '';
}
public function response_summary($state) {
return $state->answer;
}
public function was_answered($state) {
return !empty($state->answer);
}
public function set_first_step_data_elements($state, &$data) {
$data['upgradedfromdeletedquestion'] = $state->answer;
}
public function supply_missing_first_step_data(&$data) {
}
public function set_data_elements_for_step($state, &$data) {
$data['upgradedfromdeletedquestion'] = $state->answer;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,60 @@
<?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/>.
/**
* Upgrade library code for the description question type.
*
* @package qtype
* @subpackage description
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Class for converting attempt data for description questions when upgrading
* attempts to the new question engine.
*
* This class is used by the code in question/engine/upgrade/upgradelib.php.
*
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_description_qe2_attempt_updater extends question_qtype_attempt_updater {
public function right_answer() {
return '';
}
public function was_answered($state) {
return false;
}
public function response_summary($state) {
return '';
}
public function set_first_step_data_elements($state, &$data) {
}
public function supply_missing_first_step_data(&$data) {
}
public function set_data_elements_for_step($state, &$data) {
}
}
+67
View File
@@ -0,0 +1,67 @@
<?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/>.
/**
* Upgrade library code for the essay question type.
*
* @package qtype
* @subpackage essay
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Class for converting attempt data for essay questions when upgrading
* attempts to the new question engine.
*
* This class is used by the code in question/engine/upgrade/upgradelib.php.
*
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_essay_qe2_attempt_updater extends question_qtype_attempt_updater {
public function right_answer() {
return '';
}
public function response_summary($state) {
if (!empty($state->answer)) {
return $this->to_text($state->answer);
} else {
return null;
}
}
public function was_answered($state) {
return !empty($state->answer);
}
public function set_first_step_data_elements($state, &$data) {
}
public function supply_missing_first_step_data(&$data) {
}
public function set_data_elements_for_step($state, &$data) {
if (!empty($state->answer)) {
$data['answer'] = $state->answer;
}
}
}
+189
View File
@@ -0,0 +1,189 @@
<?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/>.
/**
* Upgrade library code for the match question type.
*
* @package qtype
* @subpackage match
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Class for converting attempt data for match questions when upgrading
* attempts to the new question engine.
*
* This class is used by the code in question/engine/upgrade/upgradelib.php.
*
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_match_qe2_attempt_updater extends question_qtype_attempt_updater {
protected $stems;
protected $choices;
protected $right;
protected $stemorder;
protected $choiceorder;
protected $flippedchoiceorder;
public function question_summary() {
$this->stems = array();
$this->choices = array();
$this->right = array();
foreach ($this->question->options->subquestions as $matchsub) {
$ans = $matchsub->answertext;
$key = array_search($matchsub->answertext, $this->choices);
if ($key === false) {
$key = $matchsub->id;
$this->choices[$key] = $matchsub->answertext;
}
if ($matchsub->questiontext !== '') {
$this->stems[$matchsub->id] = $this->to_text($matchsub->questiontext);
$this->right[$matchsub->id] = $key;
}
}
return $this->to_text($this->question->questiontext) . ' {' .
implode('; ', $this->stems) . '} -> {' . implode('; ', $this->choices) . '}';
}
public function right_answer() {
$answer = array();
foreach ($this->stems as $key => $stem) {
$answer[$stem] = $this->choices[$this->right[$key]];
}
return $this->make_summary($answer);
}
protected function explode_answer($answer) {
if (!$answer) {
return array();
}
$bits = explode(',', $answer);
$selections = array();
foreach ($bits as $bit) {
list($stem, $choice) = explode('-', $bit);
$selections[$stem] = $choice;
}
return $selections;
}
protected function make_summary($pairs) {
$bits = array();
foreach ($pairs as $stem => $answer) {
$bits[] = $stem . ' -> ' . $answer;
}
return implode('; ', $bits);
}
protected function lookup_choice($choice) {
foreach ($this->question->options->subquestions as $matchsub) {
if ($matchsub->code == $choice) {
if (array_key_exists($matchsub->id, $this->choices)) {
return $matchsub->id;
} else {
return array_search($matchsub->answertext, $this->choices);
}
}
}
return null;
}
public function response_summary($state) {
$choices = $this->explode_answer($state->answer);
if (empty($choices)) {
return null;
}
$pairs = array();
foreach ($choices as $stemid => $choicekey) {
if (array_key_exists($stemid, $this->stems) && $choices[$stemid]) {
$choiceid = $this->lookup_choice($choicekey);
if ($choiceid) {
$pairs[$this->stems[$stemid]] = $this->choices[$choiceid];
} else {
$this->logger->log_assumption("Dealing with a place where the
student selected a choice that was later deleted for
match question {$this->question->id}");
$pairs[$this->stems[$stemid]] = '[CHOICE THAT WAS LATER DELETED]';
}
}
}
if ($pairs) {
return $this->make_summary($pairs);
} else {
return '';
}
}
public function was_answered($state) {
$choices = $this->explode_answer($state->answer);
foreach ($choices as $choice) {
if ($choice) {
return true;
}
}
return false;
}
public function set_first_step_data_elements($state, &$data) {
$choices = $this->explode_answer($state->answer);
foreach ($choices as $key => $notused) {
if (array_key_exists($key, $this->stems)) {
$this->stemorder[] = $key;
}
}
$this->choiceorder = array_keys($this->choices);
shuffle($this->choiceorder);
$this->flippedchoiceorder = array_combine(array_values($this->choiceorder), array_keys($this->choiceorder));
$data['_stemorder'] = implode(',', $this->stemorder);
$data['_choiceorder'] = implode(',', $this->choiceorder);
}
public function supply_missing_first_step_data(&$data) {
throw new coding_exception('qtype_match_updater::supply_missing_first_step_data not tested');
$data['_stemorder'] = array_keys($this->stems);
$data['_choiceorder'] = shuffle(array_keys($this->choices));
}
public function set_data_elements_for_step($state, &$data) {
$choices = $this->explode_answer($state->answer);
foreach ($this->stemorder as $i => $key) {
if (empty($choices[$key])) {
$data['sub' . $i] = 0;
continue;
}
$choice = $this->lookup_choice($choices[$key]);
if (array_key_exists($choice, $this->flippedchoiceorder)) {
$data['sub' . $i] = $this->flippedchoiceorder[$choice] + 1;
} else {
$data['sub' . $i] = 0;
}
}
}
}
+156
View File
@@ -0,0 +1,156 @@
<?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/>.
/**
* Upgrade library code for the multichoice question type.
*
* @package qtype
* @subpackage multichoice
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Class for converting attempt data for multichoice questions when upgrading
* attempts to the new question engine.
*
* This class is used by the code in question/engine/upgrade/upgradelib.php.
*
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_multichoice_qe2_attempt_updater extends question_qtype_attempt_updater {
protected $order;
public function right_answer() {
if ($this->question->options->single) {
foreach ($this->question->options->answers as $ans) {
if ($ans->fraction > 0.999) {
return $this->to_text($ans->answer);
}
}
} else {
$rightbits = array();
foreach ($this->question->options->answers as $ans) {
if ($ans->fraction >= 0.000001) {
$rightbits[] = $this->to_text($ans->answer);
}
}
return implode('; ', $rightbits);
}
}
protected function explode_answer($answer) {
if (strpos($answer, ':') !== false) {
list($order, $responses) = explode(':', $answer);
return $responses;
} else {
// Sometimes, a bug means that a state is missing the <order>: bit,
// We need to deal with that.
$this->logger->log_assumption("Dealing with missing order information
in attempt at multiple choice question {$this->question->id}");
return $answer;
}
}
public function response_summary($state) {
$responses = $this->explode_answer($state->answer);
if ($this->question->options->single) {
if (is_numeric($responses)) {
if (array_key_exists($responses, $this->question->options->answers)) {
return $this->to_text($this->question->options->answers[$responses]->answer);
} else {
$this->logger->log_assumption("Dealing with a place where the
student selected a choice that was later deleted for
multiple choice question {$this->question->id}");
return '[CHOICE THAT WAS LATER DELETED]';
}
} else {
return null;
}
} else {
if (!empty($responses)) {
$responses = explode(',', $responses);
$bits = array();
foreach ($responses as $response) {
if (array_key_exists($response, $this->question->options->answers)) {
$bits[] = $this->to_text($this->question->options->answers[$response]->answer);
} else {
$this->logger->log_assumption("Dealing with a place where the
student selected a choice that was later deleted for
multiple choice question {$this->question->id}");
$bits[] = '[CHOICE THAT WAS LATER DELETED]';
}
}
return implode('; ', $bits);
} else {
return null;
}
}
}
public function was_answered($state) {
$responses = $this->explode_answer($state->answer);
if ($this->question->options->single) {
return is_numeric($responses);
} else {
return !empty($responses);
}
}
public function set_first_step_data_elements($state, &$data) {
if (!$state->answer) {
return;
}
list($order, $responses) = explode(':', $state->answer);
$data['_order'] = $order;
$this->order = explode(',', $order);
}
public function supply_missing_first_step_data(&$data) {
$data['_order'] = implode(',', array_keys($this->question->options->answers));
}
public function set_data_elements_for_step($state, &$data) {
$responses = $this->explode_answer($state->answer);
if ($this->question->options->single) {
if (is_numeric($responses)) {
$flippedorder = array_combine(array_values($this->order), array_keys($this->order));
if (array_key_exists($responses, $flippedorder)) {
$data['answer'] = $flippedorder[$responses];
} else {
$data['answer'] = '-1';
}
}
} else {
$responses = explode(',', $responses);
foreach ($this->order as $key => $ansid) {
if (in_array($ansid, $responses)) {
$data['choice' . $key] = 1;
} else {
$data['choice' . $key] = 0;
}
}
}
}
}
+73
View File
@@ -0,0 +1,73 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Upgrade library code for the numerical question type.
*
* @package qtype
* @subpackage numerical
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Class for converting attempt data for numerical questions when upgrading
* attempts to the new question engine.
*
* This class is used by the code in question/engine/upgrade/upgradelib.php.
*
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_numerical_qe2_attempt_updater extends question_qtype_attempt_updater {
public function right_answer() {
foreach ($this->question->options->answers as $ans) {
if ($ans->fraction > 0.999) {
return $ans->answer;
}
}
}
public function response_summary($state) {
if (!empty($state->answer)) {
return $state->answer;
} else {
return null;
}
}
public function was_answered($state) {
return !empty($state->answer);
}
public function set_first_step_data_elements($state, &$data) {
$data['_separators'] = '.$,';
}
public function supply_missing_first_step_data(&$data) {
$data['_separators'] = '.$,';
}
public function set_data_elements_for_step($state, &$data) {
if (!empty($state->answer)) {
$data['answer'] = $state->answer;
}
}
}
@@ -0,0 +1,71 @@
<?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/>.
/**
* Upgrade library code for the shortanswer question type.
*
* @package qtype
* @subpackage shortanswer
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Class for converting attempt data for shortanswer questions when upgrading
* attempts to the new question engine.
*
* This class is used by the code in question/engine/upgrade/upgradelib.php.
*
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_shortanswer_qe2_attempt_updater extends question_qtype_attempt_updater {
public function right_answer() {
foreach ($this->question->options->answers as $ans) {
if ($ans->fraction > 0.999) {
return $ans->answer;
}
}
}
public function was_answered($state) {
return !empty($state->answer);
}
public function response_summary($state) {
if (!empty($state->answer)) {
return $state->answer;
} else {
return null;
}
}
public function set_first_step_data_elements($state, &$data) {
}
public function supply_missing_first_step_data(&$data) {
}
public function set_data_elements_for_step($state, &$data) {
if (!empty($state->answer)) {
$data['answer'] = $state->answer;
}
}
}
+78
View File
@@ -0,0 +1,78 @@
<?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/>.
/**
* Upgrade library code for the truefalse question type.
*
* @package qtype
* @subpackage truefalse
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Class for converting attempt data for truefalse questions when upgrading
* attempts to the new question engine.
*
* This class is used by the code in question/engine/upgrade/upgradelib.php.
*
* @copyright 2010 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_truefalse_qe2_attempt_updater extends question_qtype_attempt_updater {
public function right_answer() {
foreach ($this->question->options->answers as $ans) {
if ($ans->fraction > 0.999) {
return $ans->answer;
}
}
}
public function response_summary($state) {
if (is_numeric($state->answer)) {
if (array_key_exists($state->answer, $this->question->options->answers)) {
return $this->question->options->answers[$state->answer]->answer;
} else {
$this->logger->log_assumption("Dealing with a place where the
student selected a choice that was later deleted for
true/false question {$this->question->id}");
return null;
}
} else {
return null;
}
}
public function was_answered($state) {
return !empty($state->answer);
}
public function set_first_step_data_elements($state, &$data) {
}
public function supply_missing_first_step_data(&$data) {
}
public function set_data_elements_for_step($state, &$data) {
if (is_numeric($state->answer)) {
$data['answer'] = (int) ($state->answer == $this->question->options->trueanswer);
}
}
}