2861328df6dd413ec9cd45be1cc806d3c083af5c
[moodle.git] / mod / quiz / db / upgrade.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Upgrade script for the quiz module.
19  *
20  * @package    mod
21  * @subpackage quiz
22  * @copyright  2006 Eloy Lafuente (stronk7)
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
27 defined('MOODLE_INTERNAL') || die();
30 /**
31  * Quiz module upgrade function.
32  * @param string $oldversion the version we are upgrading from.
33  */
34 function xmldb_quiz_upgrade($oldversion) {
35     global $CFG, $DB;
37     $dbman = $DB->get_manager();
39     // Moodle v2.2.0 release upgrade line.
40     // Put any upgrade step following this.
42     if ($oldversion < 2011120700) {
44         // Define field lastcron to be dropped from quiz_reports.
45         $table = new xmldb_table('quiz_reports');
46         $field = new xmldb_field('lastcron');
48         // Conditionally launch drop field lastcron.
49         if ($dbman->field_exists($table, $field)) {
50             $dbman->drop_field($table, $field);
51         }
53         // Quiz savepoint reached.
54         upgrade_mod_savepoint(true, 2011120700, 'quiz');
55     }
57     if ($oldversion < 2011120701) {
59         // Define field cron to be dropped from quiz_reports.
60         $table = new xmldb_table('quiz_reports');
61         $field = new xmldb_field('cron');
63         // Conditionally launch drop field cron.
64         if ($dbman->field_exists($table, $field)) {
65             $dbman->drop_field($table, $field);
66         }
68         // Quiz savepoint reached.
69         upgrade_mod_savepoint(true, 2011120701, 'quiz');
70     }
72     if ($oldversion < 2011120703) {
73         // Track page of quiz attempts.
74         $table = new xmldb_table('quiz_attempts');
76         $field = new xmldb_field('currentpage', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 0);
78         if (!$dbman->field_exists($table, $field)) {
79             $dbman->add_field($table, $field);
80         }
81         upgrade_mod_savepoint(true, 2011120703, 'quiz');
82     }
84     if ($oldversion < 2012030901) {
85         // Configuration option for navigation method.
86         $table = new xmldb_table('quiz');
88         $field = new xmldb_field('navmethod', XMLDB_TYPE_CHAR, '16', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 'free');
90         if (!$dbman->field_exists($table, $field)) {
91             $dbman->add_field($table, $field);
92         }
93         upgrade_mod_savepoint(true, 2012030901, 'quiz');
94     }
96     if ($oldversion < 2012040198) {
97         // This step was added later. In MDL-32727, it was found that adding the
98         // unique index on quiz-userid-attempt sometimes failed because of
99         // duplicate entries {quizid}-{userid}-{attempt}. We do two things to
100         // prevent these problems. First, here, we delete all preview attempts.
102         // This code is an approximate copy-and-paste from
103         // question_engine_data_mapper::delete_questions_usage_by_activities
104         // Note that, for simplicity, the MySQL performance hack has been removed.
105         // Since this code is for upgrade only, performance in not so critical,
106         // where as simplicity of testing the code is.
108         // Note that there is a limit to how far I am prepared to go in eliminating
109         // all calls to library functions in this upgrade code. The only library
110         // function still being used in question_engine::get_all_response_file_areas();
111         // I think it is pretty safe not to inline it here.
113         // Get a list of response variables that have files.
114         require_once($CFG->dirroot . '/question/type/questiontypebase.php');
115         $variables = array();
116         foreach (get_plugin_list('qtype') as $plugin => $path) {
117             $file = $path . '/questiontype.php';
118             if (!is_readable($file)) {
119                 continue;
120             }
121             include_once($file);
122             $class = 'qtype_' . $qtypename;
123             if (!class_exists($class)) {
124                 continue;
125             }
126             $qtype = new $class();
127             if (!method_exists($qtype, 'response_file_areas')) {
128                 continue;
129             }
130             $variables += $qtype->response_file_areas();
131         }
133         // Conver that to a list of actual file area names.
134         $fileareas = array();
135         foreach (array_unique($variables) as $variable) {
136             $fileareas[] = 'response_' . $variable;
137         }
138         // No point checking if this is empty as an optimisation, because essay
139         // has response file areas, so the array will never be empty.
141         // Get all the contexts where there are previews.
142         $contextids = $DB->get_records_sql_menu("
143                 SELECT DISTINCT qu.contextid, 1
144                   FROM {question_usages} qu
145                   JOIN {quiz_attempts} quiza ON quiza.uniqueid = qu.id
146                  WHERE quiza.preview = 1");
148         // Loop over contexts and files areas, deleting all files.
149         $fs = get_file_storage();
150         foreach ($contextids as $contextid => $notused) {
151             foreach ($fileareas as $filearea) {
152                 upgrade_set_timeout(300);
153                 $fs->delete_area_files_select($contextid, 'question', $filearea,
154                         "IN (SELECT qas.id
155                                FROM {question_attempt_steps} qas
156                                JOIN {question_attempts} qa ON qa.id = qas.questionattemptid
157                                JOIN {quiz_attempts} quiza ON quiza.uniqueid = qa.questionusageid
158                               WHERE quiza.preview = 1)");
159             }
160         }
162         // Now delete the question data associated with the previews.
163         $DB->delete_records_select('question_attempt_step_data', "attemptstepid IN (
164                 SELECT qas.id
165                   FROM {question_attempt_steps} qas
166                   JOIN {question_attempts} qa ON qa.id = qas.questionattemptid
167                   JOIN {quiz_attempts} quiza ON quiza.uniqueid = qa.questionusageid
168                  WHERE quiza.preview = 1)");
170         $DB->delete_records_select('question_attempt_steps', "questionattemptid IN (
171                 SELECT qa.id
172                   FROM {question_attempts} qa
173                   JOIN {quiz_attempts} quiza ON quiza.uniqueid = qa.questionusageid
174                  WHERE quiza.preview = 1)");
176         $DB->delete_records_select('question_attempts', "{question_attempts}.questionusageid IN (
177                 SELECT uniqueid FROM {quiz_attempts} WHERE preview = 1)");
179         $DB->delete_records_select('question_usages', "{question_usages}.id IN (
180                 SELECT uniqueid FROM {quiz_attempts} WHERE preview = 1)");
182         // Finally delete the previews.
183         $DB->delete_records('quiz_attempts', array('preview' => 1));
185         // Quiz savepoint reached.
186         upgrade_mod_savepoint(true, 2012040198, 'quiz');
187     }
189     if ($oldversion < 2012040199) {
190         // This step was added later. In MDL-32727, it was found that adding the
191         // unique index on quiz-userid-attempt sometimes failed because of
192         // duplicate entries {quizid}-{userid}-{attempt}.
193         // Here, if there are still duplicate entires, we renumber the values in
194         // the attempt column.
196         // Load all the problem quiz attempts.
197         $problems = $DB->get_recordset_sql('
198                 SELECT qa.id, qa.quiz, qa.userid, qa.attempt
199                   FROM {quiz_attempts} qa
200                   JOIN (
201                           SELECT DISTINCT quiz, userid
202                             FROM {quiz_attempts}
203                         GROUP BY quiz, userid, attempt
204                           HAVING COUNT(1) > 1
205                        ) problems_view ON problems_view.quiz = qa.quiz AND
206                                           problems_view.userid = qa.userid
207               ORDER BY qa.quiz, qa.userid, qa.attempt, qa.id');
209         // Renumber them.
210         $currentquiz = null;
211         $currentuserid = null;
212         $attempt = 1;
213         foreach ($problems as $problem) {
214             if ($problem->quiz !== $currentquiz || $problem->userid !== $currentuserid) {
215                 $currentquiz = $problem->quiz;
216                 $currentuserid = $problem->userid;
217                 $attempt = 1;
218             }
219             if ($attempt != $problem->attempt) {
220                 $DB->set_field('quiz_attempts', 'attempt', $attempt, array('id' => $problem->id));
221             }
222             $attempt += 1;
223         }
225         $problems->close();
227         // Quiz savepoint reached.
228         upgrade_mod_savepoint(true, 2012040199, 'quiz');
229     }
231     if ($oldversion < 2012040200) {
232         // Define index userid to be dropped form quiz_attempts
233         $table = new xmldb_table('quiz_attempts');
234         $index = new xmldb_index('userid', XMLDB_INDEX_NOTUNIQUE, array('userid'));
236         // Conditionally launch drop index quiz-userid-attempt.
237         if ($dbman->index_exists($table, $index)) {
238             $dbman->drop_index($table, $index);
239         }
241         // Quiz savepoint reached.
242         upgrade_mod_savepoint(true, 2012040200, 'quiz');
243     }
245     if ($oldversion < 2012040201) {
247         // Define key userid (foreign) to be added to quiz_attempts.
248         $table = new xmldb_table('quiz_attempts');
249         $key = new xmldb_key('userid', XMLDB_KEY_FOREIGN, array('userid'), 'user', array('id'));
251         // Launch add key userid.
252         $dbman->add_key($table, $key);
254         // Quiz savepoint reached.
255         upgrade_mod_savepoint(true, 2012040201, 'quiz');
256     }
258     if ($oldversion < 2012040202) {
260         // Define index quiz-userid-attempt (unique) to be added to quiz_attempts.
261         $table = new xmldb_table('quiz_attempts');
262         $index = new xmldb_index('quiz-userid-attempt', XMLDB_INDEX_UNIQUE, array('quiz', 'userid', 'attempt'));
264         // Conditionally launch add index quiz-userid-attempt.
265         if (!$dbman->index_exists($table, $index)) {
266             $dbman->add_index($table, $index);
267         }
269         // Quiz savepoint reached.
270         upgrade_mod_savepoint(true, 2012040202, 'quiz');
271     }
273     if ($oldversion < 2012040203) {
275         // Define field state to be added to quiz_attempts.
276         $table = new xmldb_table('quiz_attempts');
277         $field = new xmldb_field('state', XMLDB_TYPE_CHAR, '16', null, XMLDB_NOTNULL, null, 'inprogress', 'preview');
279         // Conditionally launch add field state.
280         if (!$dbman->field_exists($table, $field)) {
281             $dbman->add_field($table, $field);
282         }
284         // Quiz savepoint reached.
285         upgrade_mod_savepoint(true, 2012040203, 'quiz');
286     }
288     if ($oldversion < 2012040204) {
290         // Update quiz_attempts.state for finished attempts.
291         $DB->set_field_select('quiz_attempts', 'state', 'finished', 'timefinish > 0');
293         // Other, more complex transitions (basically abandoned attempts), will
294         // be handled by cron later.
296         // Quiz savepoint reached.
297         upgrade_mod_savepoint(true, 2012040204, 'quiz');
298     }
300     if ($oldversion < 2012040205) {
302         // Define field overduehandling to be added to quiz.
303         $table = new xmldb_table('quiz');
304         $field = new xmldb_field('overduehandling', XMLDB_TYPE_CHAR, '16', null, XMLDB_NOTNULL, null, 'autoabandon', 'timelimit');
306         // Conditionally launch add field overduehandling.
307         if (!$dbman->field_exists($table, $field)) {
308             $dbman->add_field($table, $field);
309         }
311         // Quiz savepoint reached.
312         upgrade_mod_savepoint(true, 2012040205, 'quiz');
313     }
315     if ($oldversion < 2012040206) {
317         // Define field graceperiod to be added to quiz.
318         $table = new xmldb_table('quiz');
319         $field = new xmldb_field('graceperiod', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'overduehandling');
321         // Conditionally launch add field graceperiod.
322         if (!$dbman->field_exists($table, $field)) {
323             $dbman->add_field($table, $field);
324         }
326         // Quiz savepoint reached.
327         upgrade_mod_savepoint(true, 2012040206, 'quiz');
328     }
330     return true;