From de2f6206bf139acf7836269a74020b364d00ca1c Mon Sep 17 00:00:00 2001
From: Fred Woolard <woolardfa@appstate.edu>
Date: Tue, 6 Jan 2015 17:52:35 -0500
Subject: [PATCH] Delete course content fix

---
 lib/moodlelib.php | 153 +++++++++++++++++++++++++++++++++---------------------
 1 file changed, 94 insertions(+), 59 deletions(-)

diff --git a/lib/moodlelib.php b/lib/moodlelib.php
index 68e1efc..1de641c 100644
--- a/lib/moodlelib.php
+++ b/lib/moodlelib.php
@@ -5054,73 +5054,108 @@ function remove_course_contents($courseid, $showfeedback = true, array $options
         echo $OUTPUT->notification($strdeleted.get_string('type_block_plural', 'plugin'), 'notifysuccess');
     }
 
-    // Delete every instance of every module,
-    // this has to be done before deleting of course level stuff.
-    $locations = core_component::get_plugin_list('mod');
-    foreach ($locations as $modname => $moddir) {
-        if ($modname === 'NEWMODULE') {
-            continue;
-        }
-        if ($module = $DB->get_record('modules', array('name' => $modname))) {
-            include_once("$moddir/lib.php");                 // Shows php warning only if plugin defective.
-            $moddelete = $modname .'_delete_instance';       // Delete everything connected to an instance.
-            $moddeletecourse = $modname .'_delete_course';   // Delete other stray stuff (uncommon).
-
-            if ($instances = $DB->get_records($modname, array('course' => $course->id))) {
-                foreach ($instances as $instance) {
-                    if ($cm = get_coursemodule_from_instance($modname, $instance->id, $course->id)) {
-                        // Delete activity context questions and question categories.
-                        question_delete_activity($cm,  $showfeedback);
-                    }
-                    if (function_exists($moddelete)) {
-                        // This purges all module data in related tables, extra user prefs, settings, etc.
-                        $moddelete($instance->id);
-                    } else {
-                        // NOTE: we should not allow installation of modules with missing delete support!
-                        debugging("Defective module '$modname' detected when deleting course contents: missing function $moddelete()!");
-                        $DB->delete_records($modname, array('id' => $instance->id));
-                    }
-
-                    if ($cm) {
-                        // Delete cm and its context - orphaned contexts are purged in cron in case of any race condition.
-                        context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
-                        $DB->delete_records('course_modules', array('id' => $cm->id));
-                    }
-                }
-            }
-            if (function_exists($moddeletecourse)) {
-                // Execute ptional course cleanup callback.
-                $moddeletecourse($course, $showfeedback);
-            }
-            if ($instances and $showfeedback) {
-                echo $OUTPUT->notification($strdeleted.get_string('pluginname', $modname), 'notifysuccess');
+    // Remove all data from availability and completion tables
+    // associated with course-modules belonging to this course.
+    // Note this is done even if the features are not enabled
+    // now, in case they were enabled previously.
+    $DB->delete_records_select(
+        'course_modules_completion',
+        'coursemoduleid IN (SELECT id from {course_modules} WHERE course = ?)',
+        array($courseid));
+
+    // Iterate over the list of course modules, and for
+    // each, call the associated plugin's routine to
+    // delete the instance, delete the course module
+    // entry. After the last course module for a given
+    // plugin is removed, call the plugin's broader
+    // method to delete a course, if it exists.
+    $system_modules      = core_component::get_plugin_list('mod');
+    $module_name         =
+    $module_dir          =
+    $module_del_instance =
+    $module_del_course   =  '';
+    $module_installed    = false;
+
+    // Get the course modules for this course, sorted
+    // by module name
+    $course_modules = $DB->get_records_sql(
+        "SELECT cm.*, m.name
+           FROM {course_modules} cm
+           JOIN {modules} m ON cm.module = m.id
+          WHERE cm.course = :courseid ORDER BY m.name",
+        array('courseid' => $courseid));
+
+    foreach ($course_modules as $cm) {
+
+        // Looking at a different module since last loop iteration?
+        if ($module_name != $cm->name) {
+
+            // Before setting up for the next module, call current
+            // module's course delete routine, if it exists.
+            if ($module_installed && function_exists($module_del_course)) {
+                $module_del_course($course, $showfeedback);
+            }
+
+            // Indicate finished with the current module
+            if ($module_installed && $showfeedback) {
+                echo $OUTPUT->notification($strdeleted . get_string('pluginname', $module_name), 'notifysuccess');
+            }
+
+            $module_name = $cm->name;
+
+            // Set up for the next module if it is valid
+            if (array_key_exists($module_name, $system_modules)) {
+                $module_installed    = true;
+                $module_dir          = $system_modules[$module_name];
+                $module_del_instance = $module_name . '_delete_instance';
+                $module_del_course   = $module_name . '_delete_course';
+                include_once("$module_dir/lib.php");
+            } else {
+                // This mod doesn't appear in the list of installed
+                // mods, so don't call any of its library functions
+                // even though the code might still be on disk
+                $module_installed    = false;
+                $module_dir          =
+                $module_del_instance =
+                $module_del_course   = '';
             }
-        } else {
-            // Ooops, this module is not properly installed, force-delete it in the next block.
-        }
-    }
 
-    // We have tried to delete everything the nice way - now let's force-delete any remaining module data.
+        } // if ($module_name != $cm->name)
 
-    // Remove all data from availability and completion tables that is associated
-    // with course-modules belonging to this course. Note this is done even if the
-    // features are not enabled now, in case they were enabled previously.
-    $DB->delete_records_select('course_modules_completion',
-           'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
-           array($courseid));
+        // Delete activity context questions and question categories.
+        question_delete_activity($cm, $showfeedback);
 
-    // Remove course-module data.
-    $cms = $DB->get_records('course_modules', array('course' => $course->id));
-    foreach ($cms as $cm) {
-        if ($module = $DB->get_record('modules', array('id' => $cm->module))) {
-            try {
-                $DB->delete_records($module->name, array('id' => $cm->instance));
-            } catch (Exception $e) {
-                // Ignore weird or missing table problems.
+        if ($module_installed) {
+            // If module supplies method to delete instance, call it
+            if (function_exists($module_del_instance)) {
+                $module_del_instance($cm->instance);
+            } else {
+                // Otherwise, indicate module does not provide an
+                // expected callback method, and attempt to directly
+                // remove rows using expected names for table/column
+                // squelching any resulting exceptions (e.g. table
+                // not found).
+                debugging("Defective module '{$module_name}' detected when deleting course contents: missing function {$module_del_instance}()!");
+                try { $DB->delete_records($module_name, array('id' => $cm->instance)); }
+                catch (Exception $e) {}
             }
         }
+
+        // Remove the course module row and its context, regardless of
+        // whether the module appears to be installed
         context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
         $DB->delete_records('course_modules', array('id' => $cm->id));
+
+    } // foreach $cm
+
+    // Call last module's course delete routine
+    if ($module_installed && function_exists($module_del_course)) {
+        $module_del_course($course, $showfeedback);
+    }
+
+    // Indicate finished with the last module
+    if ($module_installed && $showfeedback) {
+        echo $OUTPUT->notification($strdeleted . get_string('pluginname', $module_name), 'notifysuccess');
     }
 
     if ($showfeedback) {
-- 
1.8.4.5

