Merge branch 'MDL-32727' of git://github.com/timhunt/moodle
authorSam Hemelryk <sam@moodle.com>
Fri, 18 May 2012 04:07:20 +0000 (16:07 +1200)
committerSam Hemelryk <sam@moodle.com>
Fri, 18 May 2012 04:07:20 +0000 (16:07 +1200)
Conflicts:
mod/quiz/db/upgrade.php

1  2 
mod/quiz/db/upgrade.php

diff --combined mod/quiz/db/upgrade.php
@@@ -36,41 -36,42 +36,41 @@@ function xmldb_quiz_upgrade($oldversion
  
      $dbman = $DB->get_manager();
  
 -
 -    // Moodle v2.2.0 release upgrade line
 -    // Put any upgrade step following this
 +    // Moodle v2.2.0 release upgrade line.
 +    // Put any upgrade step following this.
  
      if ($oldversion < 2011120700) {
  
 -        // Define field lastcron to be dropped from quiz_reports
 +        // Define field lastcron to be dropped from quiz_reports.
          $table = new xmldb_table('quiz_reports');
          $field = new xmldb_field('lastcron');
  
 -        // Conditionally launch drop field lastcron
 +        // Conditionally launch drop field lastcron.
          if ($dbman->field_exists($table, $field)) {
              $dbman->drop_field($table, $field);
          }
  
 -        // quiz savepoint reached
 +        // Quiz savepoint reached.
          upgrade_mod_savepoint(true, 2011120700, 'quiz');
      }
  
      if ($oldversion < 2011120701) {
  
 -        // Define field cron to be dropped from quiz_reports
 +        // Define field cron to be dropped from quiz_reports.
          $table = new xmldb_table('quiz_reports');
          $field = new xmldb_field('cron');
  
 -        // Conditionally launch drop field cron
 +        // Conditionally launch drop field cron.
          if ($dbman->field_exists($table, $field)) {
              $dbman->drop_field($table, $field);
          }
  
 -        // quiz savepoint reached
 +        // Quiz savepoint reached.
          upgrade_mod_savepoint(true, 2011120701, 'quiz');
      }
  
      if ($oldversion < 2011120703) {
 -        // Track page of quiz attempts
 +        // Track page of quiz attempts.
          $table = new xmldb_table('quiz_attempts');
  
          $field = new xmldb_field('currentpage', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 0);
@@@ -82,7 -83,7 +82,7 @@@
      }
  
      if ($oldversion < 2012030901) {
 -        // Configuration option for navigation method
 +        // Configuration option for navigation method.
          $table = new xmldb_table('quiz');
  
          $field = new xmldb_field('navmethod', XMLDB_TYPE_CHAR, '16', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 'free');
          upgrade_mod_savepoint(true, 2012030901, 'quiz');
      }
  
+     if ($oldversion < 2012040198) {
+         // This step was added later. In MDL-32727, it was found that adding the
+         // unique index on quiz-userid-attempt sometimes failed because of
+         // duplicate entries {quizid}-{userid}-{attempt}. We do two things to
+         // prevent these problems. First, here, we delete all preview attempts.
+         // This code is an approximate copy-and-paste from
+         // question_engine_data_mapper::delete_questions_usage_by_activities
+         // Note that, for simplicity, the MySQL performance hack has been removed.
+         // Since this code is for upgrade only, performance in not so critical,
+         // where as simplicity of testing the code is.
+         // Note that there is a limit to how far I am prepared to go in eliminating
+         // all calls to library functions in this upgrade code. The only library
+         // function still being used in question_engine::get_all_response_file_areas();
+         // I think it is pretty safe not to inline it here.
+         // Get a list of response variables that have files.
+         require_once($CFG->dirroot . '/question/type/questiontypebase.php');
+         $variables = array();
+         foreach (get_plugin_list('qtype') as $plugin => $path) {
+             $file = $path . '/questiontype.php';
+             if (!is_readable($file)) {
+                 continue;
+             }
+             include_once($file);
+             $class = 'qtype_' . $qtypename;
+             if (!class_exists($class)) {
+                 continue;
+             }
+             $qtype = new $class();
+             if (!method_exists($qtype, 'response_file_areas')) {
+                 continue;
+             }
+             $variables += $qtype->response_file_areas();
+         }
+         // Conver that to a list of actual file area names.
+         $fileareas = array();
+         foreach (array_unique($variables) as $variable) {
+             $fileareas[] = 'response_' . $variable;
+         }
+         // No point checking if this is empty as an optimisation, because essay
+         // has response file areas, so the array will never be empty.
+         // Get all the contexts where there are previews.
+         $contextids = $DB->get_records_sql_menu("
+                 SELECT DISTINCT qu.contextid, 1
+                   FROM {question_usages} qu
+                   JOIN {quiz_attempts} quiza ON quiza.uniqueid = qu.id
+                  WHERE quiza.preview = 1");
+         // Loop over contexts and files areas, deleting all files.
+         $fs = get_file_storage();
+         foreach ($contextids as $contextid => $notused) {
+             foreach ($fileareas as $filearea) {
+                 upgrade_set_timeout(300);
+                 $fs->delete_area_files_select($contextid, 'question', $filearea,
+                         "IN (SELECT qas.id
+                                FROM {question_attempt_steps} qas
+                                JOIN {question_attempts} qa ON qa.id = qas.questionattemptid
+                                JOIN {quiz_attempts} quiza ON quiza.uniqueid = qa.questionusageid
+                               WHERE quiza.preview = 1)");
+             }
+         }
+         // Now delete the question data associated with the previews.
+         $DB->delete_records_select('question_attempt_step_data', "attemptstepid IN (
+                 SELECT qas.id
+                   FROM {question_attempt_steps} qas
+                   JOIN {question_attempts} qa ON qa.id = qas.questionattemptid
+                   JOIN {quiz_attempts} quiza ON quiza.uniqueid = qa.questionusageid
+                  WHERE quiza.preview = 1)");
+         $DB->delete_records_select('question_attempt_steps', "questionattemptid IN (
+                 SELECT qa.id
+                   FROM {question_attempts} qa
+                   JOIN {quiz_attempts} quiza ON quiza.uniqueid = qa.questionusageid
+                  WHERE quiza.preview = 1)");
+         $DB->delete_records_select('question_attempts', "{question_attempts}.questionusageid IN (
+                 SELECT uniqueid FROM {quiz_attempts} WHERE preview = 1)");
+         $DB->delete_records_select('question_usages', "{question_usages}.id IN (
+                 SELECT uniqueid FROM {quiz_attempts} WHERE preview = 1)");
+         // Finally delete the previews.
+         $DB->delete_records('quiz_attempts', array('preview' => 1));
+         // Quiz savepoint reached.
+         upgrade_mod_savepoint(true, 2012040198, 'quiz');
+     }
+     if ($oldversion < 2012040199) {
+         // This step was added later. In MDL-32727, it was found that adding the
+         // unique index on quiz-userid-attempt sometimes failed because of
+         // duplicate entries {quizid}-{userid}-{attempt}.
+         // Here, if there are still duplicate entires, we renumber the values in
+         // the attempt column.
+         // Load all the problem quiz attempts.
+         $problems = $DB->get_recordset_sql('
+                 SELECT qa.id, qa.quiz, qa.userid, qa.attempt
+                   FROM {quiz_attempts} qa
+                   JOIN (
+                           SELECT DISTINCT quiz, userid
+                             FROM {quiz_attempts}
+                         GROUP BY quiz, userid, attempt
+                           HAVING COUNT(1) > 1
+                        ) problems_view ON problems_view.quiz = qa.quiz AND
+                                           problems_view.userid = qa.userid
+               ORDER BY qa.quiz, qa.userid, qa.attempt, qa.id');
+         // Renumber them.
+         $currentquiz = null;
+         $currentuserid = null;
+         $attempt = 1;
+         foreach ($problems as $problem) {
+             if ($problem->quiz !== $currentquiz || $problem->userid !== $currentuserid) {
+                 $currentquiz = $problem->quiz;
+                 $currentuserid = $problem->userid;
+                 $attempt = 1;
+             }
+             if ($attempt != $problem->attempt) {
+                 $DB->set_field('quiz_attempts', 'attempt', $attempt, array('id' => $problem->id));
+             }
+             $attempt += 1;
+         }
+         $problems->close();
+         // Quiz savepoint reached.
+         upgrade_mod_savepoint(true, 2012040199, 'quiz');
+     }
      if ($oldversion < 2012040200) {
-         // Define index quiz-userid-attempt (unique) to be dropped form quiz_attempts.
+         // Define index userid to be dropped form quiz_attempts
          $table = new xmldb_table('quiz_attempts');
          $index = new xmldb_index('userid', XMLDB_INDEX_NOTUNIQUE, array('userid'));
  
 -        // Conditionally launch drop index quiz-userid-attempt
 +        // Conditionally launch drop index quiz-userid-attempt.
          if ($dbman->index_exists($table, $index)) {
              $dbman->drop_index($table, $index);
          }
  
 -        // quiz savepoint reached
 +        // Quiz savepoint reached.
          upgrade_mod_savepoint(true, 2012040200, 'quiz');
      }
  
      if ($oldversion < 2012040201) {
  
 -        // Define key userid (foreign) to be added to quiz_attempts
 +        // Define key userid (foreign) to be added to quiz_attempts.
          $table = new xmldb_table('quiz_attempts');
          $key = new xmldb_key('userid', XMLDB_KEY_FOREIGN, array('userid'), 'user', array('id'));
  
 -        // Launch add key userid
 +        // Launch add key userid.
          $dbman->add_key($table, $key);
  
 -        // quiz savepoint reached
 +        // Quiz savepoint reached.
          upgrade_mod_savepoint(true, 2012040201, 'quiz');
      }
  
      if ($oldversion < 2012040202) {
  
 -        // Define index quiz-userid-attempt (unique) to be added to quiz_attempts
 +        // Define index quiz-userid-attempt (unique) to be added to quiz_attempts.
          $table = new xmldb_table('quiz_attempts');
          $index = new xmldb_index('quiz-userid-attempt', XMLDB_INDEX_UNIQUE, array('quiz', 'userid', 'attempt'));
  
 -        // Conditionally launch add index quiz-userid-attempt
 +        // Conditionally launch add index quiz-userid-attempt.
          if (!$dbman->index_exists($table, $index)) {
              $dbman->add_index($table, $index);
          }
  
 -        // quiz savepoint reached
 +        // Quiz savepoint reached.
          upgrade_mod_savepoint(true, 2012040202, 'quiz');
      }
  
      if ($oldversion < 2012040203) {
  
 -        // Define field state to be added to quiz_attempts
 +        // Define field state to be added to quiz_attempts.
          $table = new xmldb_table('quiz_attempts');
          $field = new xmldb_field('state', XMLDB_TYPE_CHAR, '16', null, XMLDB_NOTNULL, null, 'inprogress', 'preview');
  
 -        // Conditionally launch add field state
 +        // Conditionally launch add field state.
          if (!$dbman->field_exists($table, $field)) {
              $dbman->add_field($table, $field);
          }
  
 -        // quiz savepoint reached
 +        // Quiz savepoint reached.
          upgrade_mod_savepoint(true, 2012040203, 'quiz');
      }
  
          // Other, more complex transitions (basically abandoned attempts), will
          // be handled by cron later.
  
 -        // quiz savepoint reached
 +        // Quiz savepoint reached.
          upgrade_mod_savepoint(true, 2012040204, 'quiz');
      }
  
      if ($oldversion < 2012040205) {
  
 -        // Define field overduehandling to be added to quiz
 +        // Define field overduehandling to be added to quiz.
          $table = new xmldb_table('quiz');
          $field = new xmldb_field('overduehandling', XMLDB_TYPE_CHAR, '16', null, XMLDB_NOTNULL, null, 'autoabandon', 'timelimit');
  
 -        // Conditionally launch add field overduehandling
 +        // Conditionally launch add field overduehandling.
          if (!$dbman->field_exists($table, $field)) {
              $dbman->add_field($table, $field);
          }
  
 -        // quiz savepoint reached
 +        // Quiz savepoint reached.
          upgrade_mod_savepoint(true, 2012040205, 'quiz');
      }
  
      if ($oldversion < 2012040206) {
  
 -        // Define field graceperiod to be added to quiz
 +        // Define field graceperiod to be added to quiz.
          $table = new xmldb_table('quiz');
          $field = new xmldb_field('graceperiod', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'overduehandling');
  
 -        // Conditionally launch add field graceperiod
 +        // Conditionally launch add field graceperiod.
          if (!$dbman->field_exists($table, $field)) {
              $dbman->add_field($table, $field);
          }
  
 -        // quiz savepoint reached
 +        // Quiz savepoint reached.
          upgrade_mod_savepoint(true, 2012040206, 'quiz');
      }