Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

MDL-38228 upgrade: Rewrite script that fixes corrupt course modules t…

…o sequence relation
  • Loading branch information...
commit ba5ecbc72af3766c1c72203551e8aacf91f5b442 1 parent 55e3c2c
@marinaglancy marinaglancy authored
Showing with 164 additions and 0 deletions.
  1. +20 −0 lib/db/upgrade.php
  2. +144 −0 lib/db/upgradelib.php
View
20 lib/db/upgrade.php
@@ -1552,6 +1552,8 @@ function xmldb_main_upgrade($oldversion) {
}
if ($oldversion < 2012120301.11) {
+ // This upgrade step is re-written under MDL-38228 (see below).
+ /*
// Retrieve the list of course_sections as a recordset to save memory
$coursesections = $DB->get_recordset('course_sections', null, 'course, id', 'id, course, sequence');
foreach ($coursesections as $coursesection) {
@@ -1595,6 +1597,7 @@ function xmldb_main_upgrade($oldversion) {
}
}
$coursesections->close();
+ */
// Main savepoint reached.
upgrade_main_savepoint(true, 2012120301.11);
@@ -1611,6 +1614,22 @@ function xmldb_main_upgrade($oldversion) {
}
if ($oldversion < 2012120302.01) {
+ // MDL-38228. Single script to upgrade course_modules instead of 2012120301.11.
+ // It replaces two scripts (now commented out) introduced in MDL-37939 and MDL-38173.
+
+ // This upgrade script fixes the mismatches between DB fields course_modules.section
+ // and course_sections.sequence. It makes sure that each module is included
+ // in the sequence of only one section and that course_modules.section points back to it.
+
+ // This script in included in each major version upgrade process so make sure we don't run it twice.
+ if (empty($CFG->movingmoduleupgradescriptwasrun)) {
+ upgrade_course_modules_sequences();
+
+ // To skip running the same script on the upgrade to the next major release.
+ set_config('movingmoduleupgradescriptwasrun', 1);
+ }
+
+ /*
// Retrieve the list of course_sections as a recordset to save memory.
// This is to fix a regression caused by MDL-37939.
// In this case the upgrade step is fixing records where:
@@ -1667,6 +1686,7 @@ function xmldb_main_upgrade($oldversion) {
}
}
$coursesections->close();
+ */
upgrade_main_savepoint(true, 2012120302.01);
}
View
144 lib/db/upgradelib.php
@@ -164,3 +164,147 @@ function upgrade_mysql_fix_unsigned_and_lob_columns() {
$pbar->update($i, $tablecount, "Converted unsigned/lob columns in MySQL database - $i/$tablecount.");
}
}
+
+/**
+ * This upgrade script fixes the mismatches between DB fields course_modules.section
+ * and course_sections.sequence. It makes sure that each module is included
+ * in the sequence of only one section and that course_modules.section points back to it.
+ *
+ * Orphaned modules (modules that were not included in any section sequence in this course)
+ * will be added to their sections (or 0-section if their section is not found) and
+ * made invisible since they were not accessible at all before this upgrade script.
+ *
+ * Note that this script does not remove non-existing modules from section sequences since
+ * such operation would require much more time.
+ */
+function upgrade_course_modules_sequences() {
+ global $DB;
+
+ $affectedcourses = array();
+ // Step 1. Find all modules that point to the section which does not point back to this module.
+ $sequenceconcat = $DB->sql_concat("','", "s.sequence", "','");
+ $moduleconcat = $DB->sql_concat("'%,'", "m.id", "',%'");
+ $sql = "SELECT m.id, m.course, m.section, s.sequence
+ FROM {course_modules} m LEFT OUTER JOIN {course_sections} s
+ ON m.course = s.course and m.section = s.id
+ WHERE s.sequence IS NULL OR ($sequenceconcat NOT LIKE $moduleconcat)
+ ORDER BY m.course";
+ $rs = $DB->get_recordset_sql($sql);
+ $sections = null;
+ foreach ($rs as $cm) {
+ if (!isset($sections[$cm->course])) {
+ // Retrieve all sections for the course (only once for each corrupt course).
+ $sections = array($cm->course =>
+ $DB->get_records('course_sections', array('course' => $cm->course),
+ 'section', 'id, section, sequence, visible'));
+ if (empty($sections[$cm->course])) {
+ // Very odd - the course has a module in it but has no sections. Create 0-section.
+ $newsection = array('sequence' => '', 'section' => 0, 'visible' => 1);
+ $newsection['id'] = $DB->insert_record('course_sections',
+ $newsection + array('course' => $cm->course, 'summary' => '', 'summaryformat' => FORMAT_HTML));
+ $sections[$cm->course] = array($newsection['id'] => (object)$newsection);
+ }
+ $affectedcourses[$cm->course] = true;
+ }
+ // Attempt to find the section that has this module in it's sequence.
+ // If there are several of them, pick the last because this is what get_fast_modinfo() does.
+ $sectionid = null;
+ foreach ($sections[$cm->course] as $section) {
+ if (!empty($section->sequence) && in_array($cm->id, preg_split('/,/', $section->sequence))) {
+ $sectionid = $section->id;
+ }
+ }
+ if ($sectionid) {
+ // Found the section. Update course_module to point to the correct section.
+ $params = array('id' => $cm->id, 'section' => $sectionid);
+ if (!$sections[$cm->course][$sectionid]->visible) {
+ $params['visible'] = 0;
+ }
+ $DB->update_record('course_modules', $params);
+ } else {
+ // No section in the course has this module in it's sequence.
+ if (isset($sections[$cm->course][$cm->section])) {
+ // Try to add module to the section it points to (if it is valid).
+ $sectionid = $cm->section;
+ } else {
+ // Section not found. Just add to the first available section.
+ reset($sections[$cm->course]);
+ $sectionid = key($sections[$cm->course]);
+ }
+ $newsequence = ltrim($sections[$cm->course][$sectionid]->sequence . ',' . $cm->id, ',');
+ $sections[$cm->course][$sectionid]->sequence = $newsequence;
+ $DB->update_record('course_sections', array('id' => $sectionid, 'sequence' => $newsequence));
+ // Make module invisible because it was not displayed at all before this upgrade script.
+ $DB->update_record('course_modules', array('id' => $cm->id, 'section' => $sectionid, 'visible' => 0, 'visibleold' => 0));
+ }
+ }
+ $rs->close();
+ unset($sections);
+
+ // Step 2. Find all modules that are listed in sequence of another section or listed in sequence of their section twice.
+ $sequenceconcat = $DB->sql_concat("','", "s.sequence", "','");
+ $moduleconcat = $DB->sql_concat("'%,'", "m.id", "',%'");
+ $moduleconcatdup1 = $DB->sql_concat("'%,'", "m.id", "','", "m.id", "',%'");
+ $moduleconcatdup2 = $DB->sql_concat("'%,'", "m.id", "',%,'", "m.id", "',%'");
+ $sql = "SELECT m.id, m.course, m.section AS modulesectionid,
+ s.id AS sectionid, s.sequence AS sectionsequence, s.section AS sectionsectionnum,
+ ms.section AS modulesectionnum, ms.sequence AS modulesectionsequence
+ FROM {course_modules} m JOIN {course_sections} s
+ ON m.course = s.course AND
+ (
+ (m.section <> s.id AND $sequenceconcat LIKE $moduleconcat)
+ OR
+ (m.section = s.id AND $sequenceconcat LIKE $moduleconcatdup1)
+ OR
+ (m.section = s.id AND $sequenceconcat LIKE $moduleconcatdup2)
+ )
+ JOIN {course_sections} ms ON ms.id = m.section
+ ORDER BY m.course, m.id, m.section DESC";
+ $rs = $DB->get_recordset_sql($sql);
+ $updatedsequences = array();
+ $correctmodulesections = array();
+ foreach ($rs as $cm) {
+ $incorrectsectionid = $cm->sectionid;
+ $incorrectsequence = $cm->sectionsequence;
+ if (!isset($correctmodulesections[$cm->id])) {
+ // Function get_fast_modinfo() believes that the section with the biggest sectionnum is the correct one.
+ // Let's correct everything else to match with how course is displayed to the students.
+ if ($cm->modulesectionnum > $cm->sectionsectionnum) {
+ $correctmodulesections[$cm->id] = $cm->modulesectionid;
+ } else {
+ $correctmodulesections[$cm->id] = $cm->sectionid;
+ // oops our module points to the wrong section.
+ $DB->update_record('course_modules', array('id' => $cm->id,
+ 'section' => $correctmodulesections[$cm->id]));
+ $incorrectsectionid = $cm->modulesectionid;
+ $incorrectsequence = $cm->modulesectionsequence;
+ }
+ }
+ if (isset($updatedsequences[$incorrectsectionid])) {
+ $sequence = $updatedsequences[$incorrectsectionid];
+ } else {
+ $sequence = preg_split('/,/', $incorrectsequence);
+ }
+ if ($correctmodulesections[$cm->id] <> $incorrectsectionid) {
+ // Remove all occurences of module id from section sequence.
+ $sequence = array_diff($sequence, array($cm->id));
+ } else {
+ // Remove all occurences of module id from section sequence except for the first one.
+ if (($idx = array_search($cm->id, $sequence)) !== false) {
+ $firstchunk = array_splice($sequence, 0, $idx+1);
+ $sequence = array_merge($firstchunk, array_diff($sequence, array($cm->id)));
+ }
+ }
+ $updatedsequences[$incorrectsectionid] = array_values($sequence);
+ $DB->update_record('course_sections', array('id' => $incorrectsectionid,
+ 'sequence' => join(',', $sequence)));
+ $affectedcourses[$cm->course] = true;
+ }
+ $rs->close();
+
+ // Reset course cache for affected courses.
+ if (!empty($affectedcourses)) {
+ list($sql, $params) = $DB->get_in_or_equal(array_keys($affectedcourses));
+ $DB->execute("UPDATE {course} SET modinfo = null WHERE id ".$sql, $params);
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.