From 22cd62caa9ec87ac8be5c90effd002864e44be43 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Fri, 6 May 2011 18:59:54 +0100 Subject: [PATCH] MDL-20636 Cleanup in the upgradehelper plugin. --- local/qeupgradehelper/convertquiz.php | 2 + local/qeupgradehelper/cronsetup.php | 1 + ...akeupgradetest.php => extracttestcase.php} | 498 +++++++++--------- local/qeupgradehelper/grabexample.php | 255 --------- local/qeupgradehelper/index.php | 2 +- .../lang/en/local_qeupgradehelper.php | 6 + local/qeupgradehelper/listpreupgrade.php | 49 ++ local/qeupgradehelper/listtodo.php | 2 + local/qeupgradehelper/listupgraded.php | 2 + local/qeupgradehelper/locallib.php | 29 +- local/qeupgradehelper/pretendupgrade.php | 398 ++++++-------- local/qeupgradehelper/resetquiz.php | 2 + 12 files changed, 520 insertions(+), 726 deletions(-) rename local/qeupgradehelper/{makeupgradetest.php => extracttestcase.php} (95%) delete mode 100755 local/qeupgradehelper/grabexample.php create mode 100755 local/qeupgradehelper/listpreupgrade.php diff --git a/local/qeupgradehelper/convertquiz.php b/local/qeupgradehelper/convertquiz.php index 3cd966421df..69636b726e0 100755 --- a/local/qeupgradehelper/convertquiz.php +++ b/local/qeupgradehelper/convertquiz.php @@ -34,6 +34,8 @@ $confirmed = optional_param('confirmed', false, PARAM_BOOL); require_login(); require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM)); +local_qeupgradehelper_require_upgraded(); + admin_externalpage_setup('qeupgradehelper', '', array(), local_qeupgradehelper_url('convertquiz', array('quizid' => $quizid))); $PAGE->navbar->add(get_string('listtodo', 'local_qeupgradehelper'), diff --git a/local/qeupgradehelper/cronsetup.php b/local/qeupgradehelper/cronsetup.php index 22fbe306ea6..790d5a6dec8 100755 --- a/local/qeupgradehelper/cronsetup.php +++ b/local/qeupgradehelper/cronsetup.php @@ -30,6 +30,7 @@ require_once($CFG->libdir . '/adminlib.php'); require_login(); require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM)); + admin_externalpage_setup('qeupgradehelper', '', array(), local_qeupgradehelper_url('cronsetup')); $PAGE->navbar->add(get_string('cronsetup', 'local_qeupgradehelper')); diff --git a/local/qeupgradehelper/makeupgradetest.php b/local/qeupgradehelper/extracttestcase.php similarity index 95% rename from local/qeupgradehelper/makeupgradetest.php rename to local/qeupgradehelper/extracttestcase.php index 4e5f7a0a563..2835a0ce232 100755 --- a/local/qeupgradehelper/makeupgradetest.php +++ b/local/qeupgradehelper/extracttestcase.php @@ -1,243 +1,255 @@ -. - - -/** - * This is a simple script to facilitate testing. You need to run it before - * you go to /admin/ to upgrade your database. It extracts all the data for - * one particular attempt at one question, in a form that makes it easy to - * write a unit test for upgrade logic for that particular case. - * - * (The theory is that if the upgrade dies with an error, you can restore the - * database from backup, and then use this script to extract the problem case - * as a unit test. Then you can fix that unit tests. Then you can repeat the upgrade.) - * - * @package moodlecore - * @subpackage questionengine - * @copyright 2009 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - - -require_once(dirname(__FILE__) . '/../../../config.php'); -require_once($CFG->libdir . '/questionlib.php'); -require_once($CFG->libdir . '/formslib.php'); - -// ============================================================= -// Settings form -class grab_settings_form extends moodleform { - public function definition() { - $mform = $this->_form; - - $behaviour = array( - 0 => 'Deferred feedback', - 1 => 'Interactive', - ); - - $qtypes = array( - 'ddwtos' => 'Drag-drop', - 'description' => 'Description', - 'essay' => 'Essay', - 'match' => 'Matching', - 'multichoice' => 'Multiple choice', - 'numerical' => 'Numerical', - 'opaque' => 'OpenMark', - 'oumultiresponse' => 'OU multiple-response', - 'random' => 'Random', - 'shortanswer' => 'Short-answer', - 'truefalse' => 'True/false', - ); - - $mform->addElement('header', 'h1', 'Either extract a specific question_session'); - $mform->addElement('text', 'qsid', 'Question session id', array('size' => '10')); - $mform->addElement('header', 'h2', 'Or find and extract an example by type'); - $mform->addElement('select', 'behaviour', 'Behaviour', $behaviour); - $mform->addElement('text', 'statehistory', 'State history', array('size' => '10')); - $mform->addElement('select', 'qtype', 'Question type', $qtypes); - $mform->addElement('text', 'extratests', 'Extra conditions', array('size' => '50')); - $this->add_action_buttons(false, 'Create test case'); - } -} - -print_header('Question engine upgrade test case extractor'); - -$mform = new grab_settings_form($CFG->wwwroot . '/question/engine/upgradefromoldqe/grabexample.php', null, 'get'); -if ($fromform = $mform->get_data()) { - if (!empty($fromform->qsid)) { - generate_unit_test($fromform->qsid, 'qsession' . $fromform->qsid); - } else { - notify('Searching ...', 'notifysuccess'); - flush(); - $qsid = find_test_case($fromform->behaviour, $fromform->statehistory, - $fromform->qtype, $fromform->extratests); - if ($qsid) { - generate_unit_test($qsid, 'history' . $fromform->statehistory); - } else { - notify('No suitable attempts found.'); - } - } -} - -$mform->display(); -print_footer('empty'); - -/** - * Identify the question session id of a question attempt matching certain - * requirements. - * @param integer $behaviour 0 = deferred feedback, 1 = interactive. - * @param string $statehistory of states, last first. E.g. 620. - * @param string $qtype question type. - * @return integer question_session.id. - */ -function find_test_case($behaviour, $statehistory, $qtype, $extratests) { - global $CFG; - $possibleids = get_records_sql_menu(" - SELECT - qsess.id, - 1 - - FROM {$CFG->prefix}question_sessions qsess - JOIN {$CFG->prefix}question_states qst ON qst.attempt = qsess.attemptid - AND qst.question = qsess.questionid - JOIN {$CFG->prefix}quiz_attempts quiza ON quiza.uniqueid = qsess.attemptid - JOIN {$CFG->prefix}quiz quiz ON quiz.id = quiza.quiz - JOIN {$CFG->prefix}question q ON q.id = qsess.questionid - - WHERE q.qtype = '{$qtype}' - AND quiz.optionflags = {$behaviour} - - GROUP BY - qsess.id - - HAVING SUM( - (CASE WHEN qst.event = 10 THEN 1 ELSE qst.event END) * - POWER(10, CAST(qst.seq_number AS NUMERIC(110,0))) - ) = {$statehistory} - {$extratests}", 0, 100); - - if (!$possibleids) { - return null; - } - - return array_rand($possibleids); -} - -/** - * Grab all the data that upgrade will need for upgrading one - * attempt at one question from the old DB. - */ -function generate_unit_test($questionsessionid, $namesuffix) { - $qsession = get_record('question_sessions', 'id', $questionsessionid); - $attempt = get_record('quiz_attempts', 'uniqueid', $qsession->attemptid); - $quiz = get_record('quiz', 'id', $attempt->quiz); - $qstates = get_records_select('question_states', - "attempt = {$qsession->attemptid} AND question = {$qsession->questionid}", - 'seq_number, id'); - - $question = load_question($qsession->questionid, $quiz->id); - - if ($quiz->optionflags) { - $quiz->preferredbehaviour = 'interactive'; - } else { - $quiz->preferredbehaviour = 'deferredfeedback'; - } - echo "
-    public function test_{$question->qtype}_{$quiz->preferredbehaviour}_{$namesuffix}() {
-";
-    $upgrader->display_convert_attempt_input($quiz, $attempt,
-            $question, $qsession, $qstates);
-    echo '
-        $qa = $this->updater->convert_question_attempt($quiz, $attempt, $question, $qsession, $qstates);
-
-        $expectedqa = (object) array(';
-    echo "
-            'behaviour' => '{$quiz->preferredbehaviour}',
-            'questionid' => {$question->id},
-            'maxmark' => {$question->maxmark},
-            'minfraction' => 0,
-            'flagged' => 0,
-            'questionsummary' => '',
-            'rightanswer' => '',
-            'responsesummary' => '',
-            'timemodified' => 0,
-            'steps' => array(";
-    foreach ($qstates as $state) {
-        echo "
-                {$state->seq_number} => (object) array(
-                    'sequencenumber' => {$state->seq_number},
-                    'state' => '',
-                    'fraction' => null,
-                    'timecreated' => {$state->timestamp},
-                    'userid' => {$attempt->userid},
-                    'data' => array(),
-                ),";
-    }
-    echo '
-            ),
-        );
-
-        $this->assertEqual($expectedqa, $qa);
-    }
-
'; -} - -function format_var($name, $var) { - $out = var_export($var, true); - $out = str_replace('<', '<', $out); - $out = str_replace('ADOFetchObj::__set_state(array(', '(object) array(', $out); - $out = str_replace('stdClass::__set_state(array(', '(object) array(', $out); - $out = str_replace('array (', 'array(', $out); - $out = preg_replace('/=> \n\s*/', '=> ', $out); - $out = str_replace(')),', '),', $out); - $out = str_replace('))', ')', $out); - $out = preg_replace('/\n (?! )/', "\n ", $out); - $out = preg_replace('/\n (?! )/', "\n ", $out); - $out = preg_replace('/\n (?! )/', "\n ", $out); - $out = preg_replace('/\n (?! )/', "\n ", $out); - $out = preg_replace('/\n (?! )/', "\n ", $out); - $out = preg_replace('/\n (?! )/', "\n ", $out); - $out = preg_replace('/\n (?! )/', "\n ", $out); - $out = preg_replace('/\n(?! )/', "\n ", $out); - return " $name = $out;\n"; -} - -function display_convert_attempt_input($quiz, $attempt, $question, $qsession, $qstates) { - echo format_var('$quiz', $quiz); - echo format_var('$attempt', $attempt); - echo format_var('$question', $question); - echo format_var('$qsession', $qsession); - echo format_var('$qstates', $qstates); -} - -function load_question($questionid, $quizid) { - global $CFG, $QTYPES; - - $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"); - - if (!array_key_exists($question->qtype, $QTYPES)) { - $question->qtype = 'missingtype'; - $question->questiontext = '

' . get_string('warningmissingtype', 'quiz') . '

' . $question->questiontext; - } - - $QTYPES[$question->qtype]->get_question_options($question); - - return$question; -} +. + + +/** + * This is a simple script to facilitate testing. You need to run it before + * you go to /admin/ to upgrade your database. It extracts all the data for + * one particular attempt at one question, in a form that makes it easy to + * write a unit test for upgrade logic for that particular case. + * + * (The theory is that if the upgrade dies with an error, you can restore the + * database from backup, and then use this script to extract the problem case + * as a unit test. Then you can fix that unit tests. Then you can repeat the upgrade.) + * + * @package moodlecore + * @subpackage questionengine + * @copyright 2009 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +require_once(dirname(__FILE__) . '/../../../config.php'); +require_once($CFG->libdir . '/questionlib.php'); +require_once($CFG->libdir . '/formslib.php'); + +// ============================================================= +// Settings form +class grab_settings_form extends moodleform { + public function definition() { + $mform = $this->_form; + + $behaviour = array( + 0 => 'Deferred feedback', + 1 => 'Interactive', + ); + + $qtypes = array( + 'ddwtos' => 'Drag-drop', + 'description' => 'Description', + 'essay' => 'Essay', + 'match' => 'Matching', + 'multichoice' => 'Multiple choice', + 'numerical' => 'Numerical', + 'opaque' => 'OpenMark', + 'oumultiresponse' => 'OU multiple-response', + 'random' => 'Random', + 'shortanswer' => 'Short-answer', + 'truefalse' => 'True/false', + ); + + $mform->addElement('header', 'h1', 'Either extract a specific question_session'); + $mform->addElement('text', 'qsid', 'Question session id', array('size' => '10')); + $mform->addElement('header', 'h2', 'Or find and extract an example by type'); + $mform->addElement('select', 'behaviour', 'Behaviour', $behaviour); + $mform->addElement('text', 'statehistory', 'State history', array('size' => '10')); + $mform->addElement('select', 'qtype', 'Question type', $qtypes); + $mform->addElement('text', 'extratests', 'Extra conditions', array('size' => '50')); + $this->add_action_buttons(false, 'Create test case'); + } +} + +print_header('Question engine upgrade test case extractor'); + +$mform = new grab_settings_form($CFG->wwwroot . '/question/engine/upgradefromoldqe/grabexample.php', null, 'get'); +if ($fromform = $mform->get_data()) { + if (!empty($fromform->qsid)) { + generate_unit_test($fromform->qsid, 'qsession' . $fromform->qsid); + } else { + notify('Searching ...', 'notifysuccess'); + flush(); + $qsid = find_test_case($fromform->behaviour, $fromform->statehistory, + $fromform->qtype, $fromform->extratests); + if ($qsid) { + generate_unit_test($qsid, 'history' . $fromform->statehistory); + } else { + notify('No suitable attempts found.'); + } + } +} + +$mform->display(); +print_footer('empty'); + +/** + * Identify the question session id of a question attempt matching certain + * requirements. + * @param integer $behaviour 0 = deferred feedback, 1 = interactive. + * @param string $statehistory of states, last first. E.g. 620. + * @param string $qtype question type. + * @return integer question_session.id. + */ +function find_test_case($behaviour, $statehistory, $qtype, $extratests) { + global $CFG; + $possibleids = get_records_sql_menu(" + SELECT + qsess.id, + 1 + + FROM {$CFG->prefix}question_sessions qsess + JOIN {$CFG->prefix}question_states qst ON qst.attempt = qsess.attemptid + AND qst.question = qsess.questionid + JOIN {$CFG->prefix}quiz_attempts quiza ON quiza.uniqueid = qsess.attemptid + JOIN {$CFG->prefix}quiz quiz ON quiz.id = quiza.quiz + JOIN {$CFG->prefix}question q ON q.id = qsess.questionid + + WHERE q.qtype = '{$qtype}' + AND quiz.optionflags = {$behaviour} + + GROUP BY + qsess.id + + HAVING SUM( + (CASE WHEN qst.event = 10 THEN 1 ELSE qst.event END) * + POWER(10, CAST(qst.seq_number AS NUMERIC(110,0))) + ) = {$statehistory} + {$extratests}", 0, 100); + + if (!$possibleids) { + return null; + } + + return array_rand($possibleids); +} + +/** + * Grab all the data that upgrade will need for upgrading one + * attempt at one question from the old DB. + */ +function generate_unit_test($questionsessionid, $namesuffix) { + $qsession = get_record('question_sessions', 'id', $questionsessionid); + $attempt = get_record('quiz_attempts', 'uniqueid', $qsession->attemptid); + $quiz = get_record('quiz', 'id', $attempt->quiz); + $qstates = get_records_select('question_states', + "attempt = {$qsession->attemptid} AND question = {$qsession->questionid}", + 'seq_number, id'); + + $question = load_question($qsession->questionid, $quiz->id); + + if ($quiz->optionflags) { + $quiz->preferredbehaviour = 'interactive'; + } else { + $quiz->preferredbehaviour = 'deferredfeedback'; + } + echo "
+    public function test_{$question->qtype}_{$quiz->preferredbehaviour}_{$namesuffix}() {
+";
+    $upgrader->display_convert_attempt_input($quiz, $attempt,
+            $question, $qsession, $qstates);
+
+    if ($question->qtype == 'random') {
+        list($randombit, $realanswer) = explode('-', reset($qstates)->answer, 2);
+        $newquestionid = substr($randombit, 6);
+        $newquestion = $upgrader->load_question($newquestionid);
+        $newquestion->maxmark = $question->maxmark;
+
+        echo $upgrader->format_var('$realquestion', $newquestion);
+        echo '        $this->loader->put_question_in_cache($realquestion);
+';
+    }
+
+    echo '
+        $qa = $this->updater->convert_question_attempt($quiz, $attempt, $question, $qsession, $qstates);
+
+        $expectedqa = (object) array(';
+    echo "
+            'behaviour' => '{$quiz->preferredbehaviour}',
+            'questionid' => {$question->id},
+            'maxmark' => {$question->maxmark},
+            'minfraction' => 0,
+            'flagged' => 0,
+            'questionsummary' => '',
+            'rightanswer' => '',
+            'responsesummary' => '',
+            'timemodified' => 0,
+            'steps' => array(";
+    foreach ($qstates as $state) {
+        echo "
+                {$state->seq_number} => (object) array(
+                    'sequencenumber' => {$state->seq_number},
+                    'state' => '',
+                    'fraction' => null,
+                    'timecreated' => {$state->timestamp},
+                    'userid' => {$attempt->userid},
+                    'data' => array(),
+                ),";
+    }
+    echo '
+            ),
+        );
+
+        $this->assertEqual($expectedqa, $qa);
+    }
+
'; +} + +function format_var($name, $var) { + $out = var_export($var, true); + $out = str_replace('<', '<', $out); + $out = str_replace('ADOFetchObj::__set_state(array(', '(object) array(', $out); + $out = str_replace('stdClass::__set_state(array(', '(object) array(', $out); + $out = str_replace('array (', 'array(', $out); + $out = preg_replace('/=> \n\s*/', '=> ', $out); + $out = str_replace(')),', '),', $out); + $out = str_replace('))', ')', $out); + $out = preg_replace('/\n (?! )/', "\n ", $out); + $out = preg_replace('/\n (?! )/', "\n ", $out); + $out = preg_replace('/\n (?! )/', "\n ", $out); + $out = preg_replace('/\n (?! )/', "\n ", $out); + $out = preg_replace('/\n (?! )/', "\n ", $out); + $out = preg_replace('/\n (?! )/', "\n ", $out); + $out = preg_replace('/\n (?! )/', "\n ", $out); + $out = preg_replace('/\n(?! )/', "\n ", $out); + return " $name = $out;\n"; +} + +function display_convert_attempt_input($quiz, $attempt, $question, $qsession, $qstates) { + echo format_var('$quiz', $quiz); + echo format_var('$attempt', $attempt); + echo format_var('$question', $question); + echo format_var('$qsession', $qsession); + echo format_var('$qstates', $qstates); +} + +function load_question($questionid, $quizid) { + global $CFG, $QTYPES; + + $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"); + + if (!array_key_exists($question->qtype, $QTYPES)) { + $question->qtype = 'missingtype'; + $question->questiontext = '

' . get_string('warningmissingtype', 'quiz') . '

' . $question->questiontext; + } + + $QTYPES[$question->qtype]->get_question_options($question); + + return$question; +} diff --git a/local/qeupgradehelper/grabexample.php b/local/qeupgradehelper/grabexample.php deleted file mode 100755 index 8844bf80032..00000000000 --- a/local/qeupgradehelper/grabexample.php +++ /dev/null @@ -1,255 +0,0 @@ -. - - -/** - * This script is a bit of a hack. It is like makeupgradetest.php but has been - * hacked so you can extract test cases from a different databse. - * - * To make this work, you need to fill in the details below, and add - * - * if (defined('NASTY_HACK_IGNORE_CONFIGPHP')) { - * return; - * } - * - * to the very top of your config.php file. - * - * @package moodlecore - * @subpackage questionengine - * @copyright 2009 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - - -error_reporting(E_ALL); -ini_set('display_errors', 1); -define('NASTY_HACK_IGNORE_CONFIGPHP', true); - -// Clone config.php to point at the learnacct DB read-only. -unset($CFG); // Ignore this line -$CFG = new stdClass(); - -$CFG->debug = 6143; -$CFG->debugdisplay = 1; - -// The following block points this site at learnacct database, read-only. -$CFG->dbtype = 'postgres7'; -$CFG->dbhost = ''; // TODO to use this script, complete this section -$CFG->dbname = ''; // with details of the database you want to -$CFG->dbuser = ''; // connect to. -$CFG->dbpass = ''; -$CFG->prefix = ''; - -$CFG->wwwroot = ''; // TODO to use this script, complete this section -$CFG->dirroot = ''; // with data copied from this Moodle's config.php -$CFG->dataroot = ''; -$CFG->directorypermissions = 02777; - -$CFG->admin = 'admin'; - -require_once($CFG->dirroot . '/local/ouflags/ouflags.class.php'); -$OUFLAGS = new ouflags('vle','dev'); - -require_once($CFG->dirroot . '/lib/setup.php'); -require_once($CFG->libdir . '/formslib.php'); -require_once($CFG->dirroot . '/question/engine/lib.php'); -require_once($CFG->dirroot . '/question/engine/upgradefromoldqe/upgrade.php'); - -$CFG->querylog = ''; - -// ============================================================= -// Settings form -class grab_settings_form extends moodleform { - public function definition() { - $mform = $this->_form; - - $behaviour = array( - 0 => 'Deferred feedback', - 1 => 'Interactive', - ); - - $qtypes = array( - 'ddwtos' => 'Drag-drop', - 'description' => 'Description', - 'essay' => 'Essay', - 'match' => 'Matching', - 'multichoice' => 'Multiple choice', - 'numerical' => 'Numerical', - 'opaque' => 'OpenMark', - 'oumultiresponse' => 'OU multiple-response', - 'random' => 'Random', - 'shortanswer' => 'Short-answer', - 'truefalse' => 'True/false', - ); - - $mform->addElement('header', 'h1', 'Either extract a specific question_session'); - $mform->addElement('text', 'qsid', 'Question session id', array('size' => '10')); - $mform->addElement('header', 'h2', 'Or find and extract an example by type'); - $mform->addElement('select', 'behaviour', 'Behaviour', $behaviour); - $mform->addElement('text', 'statehistory', 'State history', array('size' => '10')); - $mform->addElement('select', 'qtype', 'Question type', $qtypes); - $mform->addElement('text', 'extratests', 'Extra conditions', array('size' => '50')); - $this->add_action_buttons(false, 'Create test case'); - } -} - -class grabber_question_engine_attempt_upgrader extends question_engine_attempt_upgrader { - public function __construct() { - $this->questionloader = new question_engine_upgrade_question_loader(null); - } -} - -if ($sesskey = optional_param('sesskey', '', PARAM_RAW)) { - $USER->sesskey = $sesskey; -} - -print_header('Question engine upgrade test case extractor'); - -$mform = new grab_settings_form($CFG->wwwroot . '/question/engine/upgradefromoldqe/grabexample.php', null, 'get'); -if ($fromform = $mform->get_data()) { - if (!empty($fromform->qsid)) { - generate_unit_test($fromform->qsid, 'qsession' . $fromform->qsid); - } else { - notify('Searching ...', 'notifysuccess'); - flush(); - $qsid = find_test_case($fromform->behaviour, $fromform->statehistory, - $fromform->qtype, $fromform->extratests); - if ($qsid) { - generate_unit_test($qsid, 'history' . $fromform->statehistory); - } else { - notify('No suitable attempts found.'); - } - } -} - -$mform->display(); -print_footer('empty'); - -/** - * Identify the question session id of a question attempt matching certain - * requirements. - * @param integer $behaviour 0 = deferred feedback, 1 = interactive. - * @param string $statehistory of states, last first. E.g. 620. - * @param string $qtype question type. - * @return integer question_session.id. - */ -function find_test_case($behaviour, $statehistory, $qtype, $extratests) { - global $CFG; - $possibleids = get_records_sql_menu(" - SELECT - qsess.id, - 1 - - FROM {$CFG->prefix}question_sessions qsess - JOIN {$CFG->prefix}question_states qst ON qst.attempt = qsess.attemptid - AND qst.question = qsess.questionid - JOIN {$CFG->prefix}quiz_attempts quiza ON quiza.uniqueid = qsess.attemptid - JOIN {$CFG->prefix}quiz quiz ON quiz.id = quiza.quiz - JOIN {$CFG->prefix}question q ON q.id = qsess.questionid - - WHERE q.qtype = '{$qtype}' - AND quiz.optionflags = {$behaviour} - - GROUP BY - qsess.id - - HAVING SUM( - (CASE WHEN qst.event = 10 THEN 1 ELSE qst.event END) * - POWER(10, CAST(qst.seq_number AS NUMERIC(110,0))) - ) = {$statehistory} - {$extratests}", 0, 100); - - if (!$possibleids) { - return null; - } - - return array_rand($possibleids); -} - -/** - * Grab all the data that upgrade will need for upgrading one - * attempt at one question from the old DB. - */ -function generate_unit_test($questionsessionid, $namesuffix) { - $qsession = get_record('question_sessions', 'id', $questionsessionid); - $attempt = get_record('quiz_attempts', 'uniqueid', $qsession->attemptid); - $quiz = get_record('quiz', 'id', $attempt->quiz); - $qstates = get_records_select('question_states', - "attempt = {$qsession->attemptid} AND question = {$qsession->questionid}", - 'seq_number, id'); - - $upgrader = new grabber_question_engine_attempt_upgrader(); - - $question = $upgrader->load_question($qsession->questionid, $quiz->id); - - if ($quiz->optionflags) { - $quiz->preferredbehaviour = 'interactive'; - } else { - $quiz->preferredbehaviour = 'deferredfeedback'; - } - echo "
-    public function test_{$question->qtype}_{$quiz->preferredbehaviour}_{$namesuffix}() {
-";
-    $upgrader->display_convert_attempt_input($quiz, $attempt,
-            $question, $qsession, $qstates);
-
-    if ($question->qtype == 'random') {
-        list($randombit, $realanswer) = explode('-', reset($qstates)->answer, 2);
-        $newquestionid = substr($randombit, 6);
-        $newquestion = $upgrader->load_question($newquestionid);
-        $newquestion->maxmark = $question->maxmark;
-
-        echo $upgrader->format_var('$realquestion', $newquestion);
-        echo '        $this->loader->put_question_in_cache($realquestion);
-';
-    }
-
-    echo '
-        $qa = $this->updater->convert_question_attempt($quiz, $attempt, $question, $qsession, $qstates);
-
-        $expectedqa = (object) array(';
-    echo "
-            'behaviour' => '{$quiz->preferredbehaviour}',
-            'questionid' => {$question->id},
-            'maxmark' => {$question->maxmark},
-            'minfraction' => 0,
-            'flagged' => 0,
-            'questionsummary' => '',
-            'rightanswer' => '',
-            'responsesummary' => '',
-            'timemodified' => 0,
-            'steps' => array(";
-    foreach ($qstates as $state) {
-        echo "
-                {$state->seq_number} => (object) array(
-                    'sequencenumber' => {$state->seq_number},
-                    'state' => '',
-                    'fraction' => null,
-                    'timecreated' => {$state->timestamp},
-                    'userid' => {$attempt->userid},
-                    'data' => array(),
-                ),";
-    }
-    echo '
-            ),
-        );
-
-        $this->assertEqual($expectedqa, $qa);
-    }
-
'; -} - diff --git a/local/qeupgradehelper/index.php b/local/qeupgradehelper/index.php index 938aaf7a9af..09dcda03fb3 100755 --- a/local/qeupgradehelper/index.php +++ b/local/qeupgradehelper/index.php @@ -38,7 +38,7 @@ admin_externalpage_setup('qeupgradehelper'); $renderer = $PAGE->get_renderer('local_qeupgradehelper'); $actions = array(); -if (local_qeupgradehelper_isupgraded()) { +if (local_qeupgradehelper_is_upgraded()) { $detected = get_string('upgradedsitedetected', 'local_qeupgradehelper'); $actions[] = local_qeupgradehelper_action::make('listtodo'); $actions[] = local_qeupgradehelper_action::make('listupgraded'); diff --git a/local/qeupgradehelper/lang/en/local_qeupgradehelper.php b/local/qeupgradehelper/lang/en/local_qeupgradehelper.php index 8b670507058..d682b7a4343 100755 --- a/local/qeupgradehelper/lang/en/local_qeupgradehelper.php +++ b/local/qeupgradehelper/lang/en/local_qeupgradehelper.php @@ -41,12 +41,16 @@ $string['gotoindex'] = 'Back to the list of quizzes that can be upgraded'; $string['gotoquizreport'] = 'Go to the reports for this quiz, to check the upgrade'; $string['gotoresetlink'] = 'Go to the list of quizzes that can be reset'; $string['invalidquizid'] = 'Invaid quiz id. Either the quiz does not exist, or it has no attempts to convert.'; +$string['listpreupgrade'] = 'List quizzes and attempts'; +$string['listpreupgrade_desc'] = 'This will show a report of all the quizzes on the system and how many attempts they have. This will give you an idea of the scope of the upgrade you have to do.'; $string['listtodo'] = 'List quizzes still to upgrade'; $string['listtodo_desc'] = 'This will show a report of all the quizzes on the system (if any) that have attempts that still need to be upgraded to the new question engine.'; $string['listtodointro'] = 'These are all the quizzes with attempt data that still needs to be converted. You can convert the attempts by clicking the link.'; $string['listupgraded'] = 'List already upgrade quizzes that can be reset'; $string['listupgraded_desc'] = 'This will show a report of all the quizzes on the system whose attepmts have been upgraded, and where the old data is still present so the upgrade could be reset and redone.'; +$string['noquizattempts'] = 'Your site does not have any quiz attempts at all!'; $string['nothingupgradedyet'] = 'No upgraded attempts that can be reset'; +$string['notupgradedsiterequired'] = 'This script can only work before the site has been upgraded.'; $string['oldsitedetected'] = 'This appears to be a site that has not yet been upgraded to include the new question engine.'; $string['pluginname'] = 'Question engine upgrade helper'; $string['quizid'] = 'Quiz id'; @@ -58,3 +62,5 @@ $string['resetcomplete'] = 'Reset complete'; $string['resettingquizattempts'] = 'Resetting quiz attempts'; $string['upgradingquizattempts'] = 'Upgrading the attempts for quiz \'{$a->name}\' in course {$a->shortname}'; $string['upgradedsitedetected'] = 'This appears to be a site that has been upgraded to include the new question engine.'; +$string['upgradedsiterequired'] = 'This script can only work after the site has been upgraded.'; + diff --git a/local/qeupgradehelper/listpreupgrade.php b/local/qeupgradehelper/listpreupgrade.php new file mode 100755 index 00000000000..8f03ab8c7d9 --- /dev/null +++ b/local/qeupgradehelper/listpreupgrade.php @@ -0,0 +1,49 @@ +. + +/** + * Script to show all the quizzes in the site with how many attempts they have + * that will need to be upgraded. + * + * @package local + * @subpackage qeupgradehelper + * @copyright 2010 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +require_once(dirname(__FILE__) . '/../../config.php'); +require_once(dirname(__FILE__) . '/locallib.php'); +require_once($CFG->libdir . '/adminlib.php'); + +require_login(); +require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM)); +local_qeupgradehelper_require_not_upgraded(); + +admin_externalpage_setup('qeupgradehelper', '', array(), + local_qeupgradehelper_url('listpreupgrade')); +$PAGE->navbar->add(get_string('listpreupgrade', 'local_qeupgradehelper')); + +$renderer = $PAGE->get_renderer('local_qeupgradehelper'); + +$quizzes = local_qeupgradehelper_get_upgradable_quizzes(); + +if (empty($quizzes)) { + echo $renderer->simple_message_page(get_string('noquizattempts', 'local_qeupgradehelper')); + +} else { + echo $renderer->list_quizzes_pre_upgrade($quizzes); +} diff --git a/local/qeupgradehelper/listtodo.php b/local/qeupgradehelper/listtodo.php index f4e2d4d7bbe..ead16af8e00 100755 --- a/local/qeupgradehelper/listtodo.php +++ b/local/qeupgradehelper/listtodo.php @@ -31,6 +31,8 @@ require_once($CFG->libdir . '/adminlib.php'); require_login(); require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM)); +local_qeupgradehelper_require_upgraded(); + admin_externalpage_setup('qeupgradehelper', '', array(), local_qeupgradehelper_url('listtodo')); $PAGE->navbar->add(get_string('listtodo', 'local_qeupgradehelper')); diff --git a/local/qeupgradehelper/listupgraded.php b/local/qeupgradehelper/listupgraded.php index 072ab567279..466a82e58fc 100755 --- a/local/qeupgradehelper/listupgraded.php +++ b/local/qeupgradehelper/listupgraded.php @@ -32,6 +32,8 @@ require_once($CFG->libdir . '/adminlib.php'); require_login(); require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM)); +local_qeupgradehelper_require_upgraded(); + admin_externalpage_setup('qeupgradehelper', '', array(), local_qeupgradehelper_url('listupgraded')); $PAGE->navbar->add(get_string('listupgraded', 'local_qeupgradehelper')); diff --git a/local/qeupgradehelper/locallib.php b/local/qeupgradehelper/locallib.php index bedfb1adf94..5dca915357e 100755 --- a/local/qeupgradehelper/locallib.php +++ b/local/qeupgradehelper/locallib.php @@ -29,10 +29,33 @@ defined('MOODLE_INTERNAL') || die(); /** * Detect whether this site has been upgraded to the new question engine yet. + * @return bool whether the site has been upgraded. */ -function local_qeupgradehelper_isupgraded() { - global $CFG; - return is_readable($CFG->dirroot . '/question/engine/upgrade/upgradelib.php'); +function local_qeupgradehelper_is_upgraded() { + global $CFG, $DB; + $dbman = $DB->get_manager(); + return is_readable($CFG->dirroot . '/question/engine/upgrade/upgradelib.php') && + $dbman->table_exists('question_usages'); +} + +/** + * If the site has not yet been upgraded, display an error. + */ +function local_qeupgradehelper_require_upgraded() { + if (!local_qeupgradehelper_is_upgraded()) { + throw new moodle_exception('upgradedsiterequired', 'local_qeupgradehelper', + local_qeupgradehelper_url('index')); + } +} + +/** + * If the site has been upgraded, display an error. + */ +function local_qeupgradehelper_require_not_upgraded() { + if (!local_qeupgradehelper_is_upgraded()) { + throw new moodle_exception('notupgradedsiterequired', 'local_qeupgradehelper', + local_qeupgradehelper_url('index')); + } } /** diff --git a/local/qeupgradehelper/pretendupgrade.php b/local/qeupgradehelper/pretendupgrade.php index 651e17ac161..b399f99ff90 100755 --- a/local/qeupgradehelper/pretendupgrade.php +++ b/local/qeupgradehelper/pretendupgrade.php @@ -1,224 +1,174 @@ -. - - -/** - * This script is a bit of a hack. It connects to another database and tries to - * run most of the question engine upgrade. That is, it loads the old data, and - * tries to convert it to the new structure, but it does not try to output it - * at all. - * - * The idea is that this should find most of the logic errors, since the code to - * save the new data to the DB is quite simple. - * - * To make this work, you need to fill in the details below, and add - * - * if (defined('NASTY_HACK_IGNORE_CONFIGPHP')) { - * return; - * } - * - * to the very top of your config.php file. - * - * @package moodlecore - * @subpackage questionengine - * @copyright 2010 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - - -error_reporting(E_ALL); -ini_set('display_errors', 1); -define('NASTY_HACK_IGNORE_CONFIGPHP', true); - -// Clone config.php to point at the learnacct DB read-only. -unset($CFG); // Ignore this line -$CFG = new stdClass(); - -$CFG->debug = 6143; -$CFG->debugdisplay = 1; - -// The following block points this site at learnacct database, read-only. -$CFG->dbtype = 'postgres7'; -$CFG->dbhost = ''; // TODO to use this script, complete this section -$CFG->dbname = ''; // with details of the database you want to -$CFG->dbuser = ''; // connect to. -$CFG->dbpass = ''; -$CFG->prefix = ''; - -$CFG->wwwroot = ''; // TODO to use this script, complete this section -$CFG->dirroot = ''; // with data copied from this Moodle's config.php -$CFG->dataroot = ''; -$CFG->directorypermissions = 02777; - -$CFG->admin = 'admin'; - -require_once($CFG->dirroot . '/local/ouflags/ouflags.class.php'); -$OUFLAGS = new ouflags('vle','dev'); - -require_once($CFG->dirroot . '/lib/setup.php'); -require_once($CFG->libdir . '/formslib.php'); -require_once($CFG->dirroot . '/question/engine/lib.php'); -require_once($CFG->dirroot . '/question/engine/upgradefromoldqe/upgrade.php'); - -$CFG->querylog = ''; -$CFG->rcache = false; -raise_memory_limit('1024M'); - -// ============================================================= - -class pretend_question_engine_attempt_upgrader extends question_engine_attempt_upgrader { - public $fromquiz = 0; - public $toquiz = 1000000; - public $qsdone = 0; - - protected function get_quiz_ids() { - return get_records_select_menu('quiz', - "id >= {$this->fromquiz} AND id < {$this->toquiz}", 'id', 'id,1'); - } - - public function get_attemtps_where($quizid) { - return "quiz = {$quizid} AND preview = 0"; - } - - protected function set_quba_preferred_behaviour($qubaid, $preferredbehaviour) { - return true; - } - - protected function set_quiz_attempt_layout($qubaid, $layout) { - return true; - } - - protected function delete_quiz_attempt($qubaid) { - return true; - } - - protected function insert_record($table, $record, $saveid = true) { - if ($table == 'question_attempts') { - if ($this->toquiz - $this->fromquiz <= 10) { - echo "saving qa from {$record->_fromqsession} "; - } else { - echo 'S'; - } - } - $this->escape_fields($record); - if ($table == 'question_attempt_steps' && is_null($record->sequencenumber)) { - notify('Null sequencenumber found.'); - } - if ($saveid) { - $record->id = 666; - } - return true; - } - - protected function convert_quiz_attempt($quiz, $attempt, $questionsessionsrs, $questionsstatesrs) { - if (empty($quiz->preferredbehaviour)) { - if ($quiz->optionflags == 0) { - $quiz->preferredbehaviour = 'deferredfeedback'; - } else { - $quiz->preferredbehaviour = 'interactive'; - } - } - return parent::convert_quiz_attempt($quiz, $attempt, $questionsessionsrs, $questionsstatesrs); - } - - public function convert_question_attempt($quiz, $attempt, $question, $qsession, $qstates) { - if ($this->toquiz - $this->fromquiz <= 10) { - if ($this->qsdone % 10 == 0) { - echo '
'; - } - echo "qs {$qsession->id} "; - } else { - if ($this->qsdone % 100 == 0) { - echo '
'; - } - echo "C"; - } - $qa = parent::convert_question_attempt($quiz, $attempt, $question, $qsession, $qstates); - $qa->_fromqsession = $qsession->id; - $this->qsdone++; - return $qa; - } - - public function supply_missing_question_attempt($quiz, $attempt, $question) { - if ($this->toquiz - $this->fromquiz <= 10) { - if ($this->qsdone % 10 == 0) { - echo '
'; - } - echo "missing {$question->id} "; - } else { - if ($this->qsdone % 100 == 0) { - echo '
'; - } - echo "M"; - } - $qa = parent::supply_missing_question_attempt($quiz, $attempt, $question); - $qa->_fromqsession = 'missing'; - return $qa; - } - - protected function print_progress($done, $outof, $quizid) { - echo "\n\n

Quiz {$done}/{$outof} ({$quizid})

\n\n
"; - gc_collect_cycles(); - echo '

Current memory usage: ' . memory_get_usage() . '/' . memory_get_peak_usage() . '

'; - } -} - -// ============================================================= - -$fromquiz = required_param('from', PARAM_INT); -$toquiz = optional_param('to', $fromquiz + 1, PARAM_INT); - -print_header('Question engine upgrade tester'); -echo "\n\n

Starting pretend upgrade of database '$CFG->dbhost', prefix '$CFG->prefix' on host '$CFG->dbhost'.

\n\n
"; - -$timestart = time(); -$qsconverted = do_pretend_upgrade($fromquiz, $toquiz); -$totaltime = time() - $timestart; -echo "
\n\n

{$qsconverted} question sessions converted in {$totaltime} seconds.

\n\n"; - -if ($qsconverted > 0) { - echo "

Estimate for 5 million: " . format_time(5000000 / $qsconverted * $totaltime) . "

\n\n"; -} - -$number = $toquiz - $fromquiz; -echo "
\n\n

"; -if (record_exists_select('quiz', "id < {$fromquiz}")) { - $newfrom = $fromquiz - $number; - echo "Previous {$number} quizzes "; -} -if (record_exists_select('quiz', "id >= {$toquiz}")) { - $newto = $toquiz + $number; - echo "Next {$number} quizzes "; -} -echo "

\n\n"; - -print_footer('empty'); - -function do_pretend_upgrade($fromquiz, $toquiz) { - $upgrader = new pretend_question_engine_attempt_upgrader(); - $upgrader->fromquiz = $fromquiz; - $upgrader->toquiz = $toquiz; - -// xhprof_enable(XHPROF_FLAGS_MEMORY + XHPROF_FLAGS_NO_BUILTINS); - - $upgrader->convert_all_quiz_attempts(); - -// $xhprof_data = xhprof_disable(); -// print_object($xhprof_data); - - return $upgrader->qsdone; -} +. + + +/** + * This script is a bit of a hack. It connects to another database and tries to + * run most of the question engine upgrade. That is, it loads the old data, and + * tries to convert it to the new structure, but it does not try to output it + * at all. + * + * The idea is that this should find most of the logic errors, since the code to + * save the new data to the DB is quite simple. + * + * @package moodlecore + * @subpackage questionengine + * @copyright 2010 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +raise_memory_limit('1024M'); + +// ============================================================= + +class pretend_question_engine_attempt_upgrader extends question_engine_attempt_upgrader { + public $fromquiz = 0; + public $toquiz = 1000000; + public $qsdone = 0; + + protected function get_quiz_ids() { + return get_records_select_menu('quiz', + "id >= {$this->fromquiz} AND id < {$this->toquiz}", 'id', 'id,1'); + } + + public function get_attemtps_where($quizid) { + return "quiz = {$quizid} AND preview = 0"; + } + + protected function set_quba_preferred_behaviour($qubaid, $preferredbehaviour) { + return true; + } + + protected function set_quiz_attempt_layout($qubaid, $layout) { + return true; + } + + protected function delete_quiz_attempt($qubaid) { + return true; + } + + protected function insert_record($table, $record, $saveid = true) { + if ($table == 'question_attempts') { + if ($this->toquiz - $this->fromquiz <= 10) { + echo "saving qa from {$record->_fromqsession} "; + } else { + echo 'S'; + } + } + $this->escape_fields($record); + if ($table == 'question_attempt_steps' && is_null($record->sequencenumber)) { + notify('Null sequencenumber found.'); + } + if ($saveid) { + $record->id = 666; + } + return true; + } + + protected function convert_quiz_attempt($quiz, $attempt, $questionsessionsrs, $questionsstatesrs) { + if (empty($quiz->preferredbehaviour)) { + if ($quiz->optionflags == 0) { + $quiz->preferredbehaviour = 'deferredfeedback'; + } else { + $quiz->preferredbehaviour = 'interactive'; + } + } + return parent::convert_quiz_attempt($quiz, $attempt, $questionsessionsrs, $questionsstatesrs); + } + + public function convert_question_attempt($quiz, $attempt, $question, $qsession, $qstates) { + if ($this->toquiz - $this->fromquiz <= 10) { + if ($this->qsdone % 10 == 0) { + echo '
'; + } + echo "qs {$qsession->id} "; + } else { + if ($this->qsdone % 100 == 0) { + echo '
'; + } + echo "C"; + } + $qa = parent::convert_question_attempt($quiz, $attempt, $question, $qsession, $qstates); + $qa->_fromqsession = $qsession->id; + $this->qsdone++; + return $qa; + } + + public function supply_missing_question_attempt($quiz, $attempt, $question) { + if ($this->toquiz - $this->fromquiz <= 10) { + if ($this->qsdone % 10 == 0) { + echo '
'; + } + echo "missing {$question->id} "; + } else { + if ($this->qsdone % 100 == 0) { + echo '
'; + } + echo "M"; + } + $qa = parent::supply_missing_question_attempt($quiz, $attempt, $question); + $qa->_fromqsession = 'missing'; + return $qa; + } + + protected function print_progress($done, $outof, $quizid) { + echo "\n\n

Quiz {$done}/{$outof} ({$quizid})

\n\n
"; + gc_collect_cycles(); + echo '

Current memory usage: ' . memory_get_usage() . '/' . memory_get_peak_usage() . '

'; + } +} + +// ============================================================= + +$fromquiz = required_param('from', PARAM_INT); +$toquiz = optional_param('to', $fromquiz + 1, PARAM_INT); + +print_header('Question engine upgrade tester'); +echo "\n\n

Starting pretend upgrade of database '$CFG->dbhost', prefix '$CFG->prefix' on host '$CFG->dbhost'.

\n\n
"; + +$timestart = time(); +$qsconverted = do_pretend_upgrade($fromquiz, $toquiz); +$totaltime = time() - $timestart; +echo "
\n\n

{$qsconverted} question sessions converted in {$totaltime} seconds.

\n\n"; + +if ($qsconverted > 0) { + echo "

Estimate for 5 million: " . format_time(5000000 / $qsconverted * $totaltime) . "

\n\n"; +} + +$number = $toquiz - $fromquiz; +echo "
\n\n

"; +if (record_exists_select('quiz', "id < {$fromquiz}")) { + $newfrom = $fromquiz - $number; + echo "Previous {$number} quizzes "; +} +if (record_exists_select('quiz', "id >= {$toquiz}")) { + $newto = $toquiz + $number; + echo "Next {$number} quizzes "; +} +echo "

\n\n"; + +print_footer('empty'); + +function do_pretend_upgrade($fromquiz, $toquiz) { + $upgrader = new pretend_question_engine_attempt_upgrader(); + $upgrader->fromquiz = $fromquiz; + $upgrader->toquiz = $toquiz; + + $upgrader->convert_all_quiz_attempts(); + + return $upgrader->qsdone; +} diff --git a/local/qeupgradehelper/resetquiz.php b/local/qeupgradehelper/resetquiz.php index 9a9e8e08845..6f1f9218187 100755 --- a/local/qeupgradehelper/resetquiz.php +++ b/local/qeupgradehelper/resetquiz.php @@ -33,6 +33,8 @@ $confirmed = optional_param('confirmed', false, PARAM_BOOL); require_login(); require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM)); +local_qeupgradehelper_require_upgraded(); + admin_externalpage_setup('qeupgradehelper', '', array(), local_qeupgradehelper_url('resetquiz', array('quizid' => $quizid))); $PAGE->navbar->add(get_string('listupgraded', 'local_qeupgradehelper'),