]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-14378 When deleting course category deal with everything that depends on its...
authorskodak <skodak>
Tue, 13 May 2008 21:52:38 +0000 (21:52 +0000)
committerskodak <skodak>
Tue, 13 May 2008 21:52:38 +0000 (21:52 +0000)
course/delete_category_form.php [new file with mode: 0644]
course/editcategory.php
course/index.php
course/lib.php
lang/en_utf8/error.php
lang/en_utf8/moodle.php
lang/en_utf8/question.php
lib/gradelib.php
lib/questionlib.php

diff --git a/course/delete_category_form.php b/course/delete_category_form.php
new file mode 100644 (file)
index 0000000..f25194f
--- /dev/null
@@ -0,0 +1,97 @@
+<?php  //$Id$
+
+require_once($CFG->libdir.'/formslib.php');
+
+class delete_category_form extends moodleform {
+
+    var $_category;
+
+    function definition() {
+        global $CFG;
+
+        $mform    =& $this->_form;
+        $category = $this->_customdata;
+        $this->_category = $category;
+
+        $mform->addElement('header','general', get_string('categorycurrentcontents', '', format_string($category->name)));
+
+        $displaylist = array();
+        $parentlist = array();
+        $children = array();
+        make_categories_list($displaylist, $parentlist);
+        unset($displaylist[$category->id]);
+        foreach ($displaylist as $catid=>$unused) {
+            // remove all children of $category
+            if (isset($parentlist[$catid]) and in_array($category->id, $parentlist[$catid])) {
+                $children[] = $catid;
+                unset($displaylist[$catid]);
+                continue;
+            }
+            if (!has_capability('moodle/course:create', get_context_instance(CONTEXT_COURSECAT, $catid))) {
+                unset($displaylist[$catid]);
+            }
+        }
+
+        $candeletecontent = true;
+        foreach ($children as $catid) {
+            $context = get_context_instance(CONTEXT_COURSECAT, $catid);
+            if (!has_capability('moodle/category:delete', $context)) {
+                $candeletecontent = false;
+                break;
+            }
+        }
+
+        $options = array();
+
+        if ($displaylist) {
+            $options[0] = get_string('move');
+        }
+
+        if ($candeletecontent) {
+            $options[1] =get_string('delete');
+        }
+
+        if (empty($options)) {
+            print_error('nocategorydelete', 'error', 'index.php', format_string($category->name));
+        }
+
+        $mform->addElement('select', 'fulldelete', get_string('categorycontents'), $options);
+        $mform->disabledIf('newparent', 'fulldelete', 'eq', '1');
+        $mform->setDefault('newparent', 0);
+
+        if ($displaylist) {
+            $mform->addElement('select', 'newparent', get_string('movecategorycontentto'), $displaylist);
+            if (in_array($category->parent, $displaylist)) {
+                $mform->setDefault('newparent', $category->parent);
+            }
+        }
+
+        $mform->addElement('hidden', 'delete');
+        $mform->addElement('hidden', 'sure');
+        $mform->setDefault('sure', md5(serialize($category)));
+
+//--------------------------------------------------------------------------------
+        $this->add_action_buttons(true, get_string('delete'));
+
+    }
+
+/// perform some extra moodle validation
+    function validation($data, $files) {
+        $errors = parent::validation($data, $files);
+
+        if (!empty($data['fulldelete'])) {
+            // already verified
+        } else {
+            if (empty($data['newparent'])) {
+                $errors['newparent'] = get_string('required');
+            }
+        }
+
+        if ($data['sure'] != md5(serialize($this->_category))) {
+            $errors['categorylabel'] = get_string('categorymodifiedcancel');
+        }
+
+        return $errors;
+    }
+}
+?>
index becceb67d09a4b9e1a55b71b3155d2e8efc79570..0292caedf0526626576f337c8cafcd51eb6a5930 100644 (file)
@@ -72,7 +72,7 @@ if ($mform->is_cancelled()){
         } else {
             $newcategory->context = get_context_instance(CONTEXT_COURSECAT, $newcategory->id);
             mark_context_dirty($newcategory->context->path);
-            redirect('index.php?categoryedit=on', get_string('categoryadded', null, stripslashes($newcategory->name)));
+            redirect('index.php?categoryedit=on');
         }
     } elseif (has_capability('moodle/category:update', $context)) {
         $newcategory->id = $category->id;
index 4e09ab5cb93b7bd2dd05f5f025568370de206cc6..df2cdc4bbcc5259d979ba4ea1ad58da514b12ea7 100644 (file)
@@ -9,12 +9,11 @@
     $delete   = optional_param('delete',0,PARAM_INT);
     $hide     = optional_param('hide',0,PARAM_INT);
     $show     = optional_param('show',0,PARAM_INT);
-    $sure     = optional_param('sure','',PARAM_ALPHANUM);
     $move     = optional_param('move',0,PARAM_INT);
     $moveto   = optional_param('moveto',-1,PARAM_INT);
     $moveup   = optional_param('moveup',0,PARAM_INT);
     $movedown = optional_param('movedown',0,PARAM_INT);
-    
+
     $sysctx  = get_context_instance(CONTEXT_SYSTEM);
     $context = $sysctx;
 
@@ -77,7 +76,7 @@
             print_box_end();
         }
 
-        /// I am not sure this context in the next has_capability call is correct. 
+        /// I am not sure this context in the next has_capability call is correct.
         if (isloggedin() and !isguest() and !has_capability('moodle/course:create', $sysctx) and $CFG->enablecourserequests) {  // Print link to request a new course
             print_single_button('request.php', NULL, get_string('courserequest'), 'get');
         }
 
 /// From now on is all the admin/course creator functions
 
-/// Print headings
+/// Delete a category if necessary
 
-    if (has_capability('moodle/site:config', $sysctx)) {
-        require_once($CFG->libdir.'/adminlib.php');
-        admin_externalpage_setup('coursemgmt');
-        admin_externalpage_print_header();
-    } else {
-        print_header("$site->shortname: $strcategories", $strcourses,
-            build_navigation(array(array('name'=>$strcategories,'link'=>'','type'=>'misc'))), '', '', true, update_categories_button());
-    }
+    if (!empty($delete) and confirm_sesskey()) {
+        require_once('delete_category_form.php');
 
-    print_heading($strcategories);
+        if (!$deletecat = get_record('course_categories', 'id', $delete)) {
+            error('Incorrect category id', 'index.php');
+        }
 
-/// Delete a category if necessary
+        $heading = get_string('deletecategory', '', format_string($deletecat->name));
 
-    if (!empty($delete) and confirm_sesskey()) {
+        $context = get_context_instance(CONTEXT_COURSECAT, $delete);
+        require_capability('moodle/category:delete', $context);
 
-          // context is coursecat, if not present admins should have it set in site level
-         $context = get_context_instance(CONTEXT_COURSECAT, $delete);
-        if ($deletecat = get_record('course_categories', 'id', $delete) and has_capability('moodle/category:delete', $context)) {
-            if (!empty($sure) && $sure == md5($deletecat->timemodified)) {
-                /// Send the children categories to live with their grandparent
-                if ($childcats = get_records('course_categories', 'parent', $deletecat->id)) {
-                    foreach ($childcats as $childcat) {
-                        if (! set_field('course_categories', 'parent', $deletecat->parent, 'id', $childcat->id)) {
-                            print_error('cannotupdatesubcate', '', 'index.php');
-                        }
-                    }
-                }
+        $mform = new delete_category_form(null, $deletecat);
+        $mform->set_data(array('delete'=>$delete));
 
-                ///  If the grandparent is a valid (non-zero) category, then
-                ///  send the children courses to live with their grandparent as well
-                if ($deletecat->parent) {
-                    if ($childcourses = get_records('course', 'category', $deletecat->id)) {
-                        foreach ($childcourses as $childcourse) {
-                            if (! set_field('course', 'category', $deletecat->parent, 'id', $childcourse->id)) {
-                                print_error('cannotupdatesubcourse', '', 'index.php');
-                            }
-                        }
-                    }
-                }
+        if ($mform->is_cancelled()) {
+            redirect('index.php');
 
-                /// Finally delete the category itself
-                if (delete_records('course_categories', 'id', $deletecat->id)) {
-                    notify(get_string('categorydeleted', '', format_string($deletecat->name)));
-                    // MLD-9983
-                    events_trigger('category_deleted', $deletecat);
-                }
-            }
-            else {
-                $strdeletecategorycheck = get_string('deletecategorycheck','', format_string($deletecat->name));
-                notice_yesno($strdeletecategorycheck,
-                             "index.php?delete=$delete&amp;sure=".md5($deletecat->timemodified)."&amp;sesskey=$USER->sesskey",
-                             "index.php?sesskey=$USER->sesskey");
-
-                print_footer();  
-                exit();
+        } else if (!$data= $mform->get_data(false)) {
+            require_once($CFG->libdir . '/questionlib.php');
+            print_category_edit_header();
+            print_heading($heading);
+            print_box(get_string('deletecategorycheck2'), 'generalbox boxwidthnormal boxaligncenter');
+            if (question_context_has_any_questions($context)) {
+                print_box(get_string('deletecoursecategorywithquestions', 'question'), 
+                        'generalbox boxwidthnormal boxaligncenter');
             }
+            $mform->display();
+            print_footer();
+            exit();
         }
+
+        print_category_edit_header();
+        print_heading($heading);
+
+        if ($data->fulldelete) {
+            category_delete_full($deletecat, true);
+        } else {
+            category_delete_move($deletecat, $data->newparent, true);
+        }
+
+        print_continue('index.php');
+
+        print_footer();
+        die;
     }
 
+/// Print headings
+    print_category_edit_header();
+    print_heading($strcategories);
+
 
 /// Create a default category if necessary
     if (!$categories = get_categories()) {    /// No category yet!
     echo '</table>';
 
     echo '<div class="buttons">';
-    
+
     if (!empty($category->id)) {
         // Print link to create a new course in current category
         if (has_capability('moodle/course:create', $context)) {
             print_single_button('edit.php', $options, get_string('addnewcourse'), 'get');
         }
     }else{
-        if (has_capability('moodle/course:create', $sysctx)) {  
+        if (has_capability('moodle/course:create', $sysctx)) {
             // print create course link to first category
             $options = array();
             $options = array('category' => get_field('course_categories', 'id', 'parent', '0'));
@@ -428,4 +419,17 @@ function print_category_edit($category, $displaylist, $parentslist, $depth=-1, $
         }
     }
 }
+
+function print_category_edit_header() {
+    global $CFG;
+
+    if (has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) {
+        require_once($CFG->libdir.'/adminlib.php');
+        admin_externalpage_setup('coursemgmt');
+        admin_externalpage_print_header();
+    } else {
+        print_header("$site->shortname: $strcategories", get_string('courses'),
+            build_navigation(array(array('name'=>get_string('categories'),'link'=>'','type'=>'misc'))), '', '', true, update_categories_button());
+    }
+}
 ?>
index 07a2af96041919f7187af65e8d6c209e6b622e40..0d59719ceeba74834365f8ad528a54694e27f129 100644 (file)
@@ -2701,6 +2701,103 @@ function course_allowed_module($course,$mod) {
     return (record_exists('course_allowed_modules','course',$course->id,'module',$modid));
 }
 
+/**
+ * Recursively delete category including all subcategories and courses.
+ * @param object $ccategory
+ * @return bool status
+ */
+function category_delete_full($category, $showfeedback=true) {
+    global $CFG;
+    require_once($CFG->libdir.'/gradelib.php');
+    require_once($CFG->libdir.'/questionlib.php');
+
+    if ($children = get_records('course_categories', 'parent', $category->id, 'sortorder ASC')) {
+        foreach ($children as $childcat) {
+            if (!category_delete_full($childcat, $showfeedback)) {
+                notify("Error deleting category $childcat->name"); 
+                return false;
+            }
+        }
+    }
+
+    if ($courses = get_records('course', 'category', $category->id, 'sortorder ASC')) {
+        foreach ($courses as $course) {
+            if (!delete_course($course->id, false)) {
+                notify("Error deleting course $course->shortname"); 
+                return false;
+            }
+            notify("Deleted course $course->shortname", 'notifysuccess'); // TODO: localize 
+        }
+    }
+
+    // now delete anything that may depend on course category context
+    grade_course_category_delete($category->id, 0, $showfeedback);
+    if (!question_delete_course_category($category, 0, $showfeedback)) {
+        notify(get_string('errordeletingquestionsfromcategory', 'question', $category), 'notifysuccess'); 
+        return false;
+    }
+
+    // finally delete the category and it's context
+    delete_records('course_categories', 'id', $category->id);
+    delete_context(CONTEXT_COURSECAT, $category->id);
+
+    events_trigger('category_deleted', $category);
+
+    notify("Deleted category $category->name", 'notifysuccess'); // TODO: localize 
+
+    return true;
+}
+
+/**
+ * Delete category, but move contents to another category.
+ * @param object $ccategory
+ * @param int $newparentid category id
+ * @return bool status
+ */
+function category_delete_move($category, $newparentid, $showfeedback=true) {
+    global $CFG;
+    require_once($CFG->libdir.'/gradelib.php');
+    require_once($CFG->libdir.'/questionlib.php');
+
+    if (!$newparentcat = get_record('course_categories', 'id', $newparentid)) {
+        return false;
+    }
+
+    if ($children = get_records('course_categories', 'parent', $category->id, 'sortorder ASC')) {
+        foreach ($children as $childcat) {
+            if (!move_category($childcat, $newparentcat)) {
+                notify("Error moving category $childcat->name"); 
+                return false;
+            }
+        }
+    }
+
+    if ($courses = get_records('course', 'category', $category->id, 'sortorder ASC', 'id')) {
+        if (!move_courses(array_keys($courses), $newparentid)) {
+            notify("Error moving courses"); 
+            return false;
+        }
+        notify("Moved courses from $category->name", 'notifysuccess'); // TODO: localize 
+    }
+
+    // now delete anything that may depend on course category context
+    grade_course_category_delete($category->id, $newparentid, $showfeedback);
+    if (!question_delete_course_category($category, $newparentcat, $showfeedback)) {
+        notify(get_string('errordeletingquestionsfromcategory', 'question', $category), 'notifysuccess'); 
+        return false;
+    }
+
+    // finally delete the category and it's context
+    delete_records('course_categories', 'id', $category->id);
+    delete_context(CONTEXT_COURSECAT, $category->id);
+
+    events_trigger('category_deleted', $category);
+
+    notify("Deleted category $category->name", 'notifysuccess'); // TODO: localize 
+
+    return true;
+}
+
 /***
  *** Efficiently moves many courses around while maintaining
  *** sortorder in order.
index 39f51c1475830ba9b3d289944d1b315e4aa46530..3b5cd26764b1d42df345a4ea73ee37338365f096 100644 (file)
@@ -170,6 +170,7 @@ $string['needcopy'] = 'You need to copy something first!';
 $string['needcoursecategroyid'] = 'Either course id or category must be specified';
 $string['noblocks'] = 'No blocks found!';
 $string['noformdesc'] = 'No formslib form description file found for this activity.';
+$string['nocategorydelete'] = 'Category \'$a\' can not be deleted!';
 $string['nocontext'] = 'Sorry, but that course is not a valid context';
 $string['noinstances'] = 'There are no instances of $a in this course!';
 $string['noguest'] = 'No guests here!';
index 1a8b63962915f24f289e6281ced4c75f6fe80714..8221160975c54ca5b8fbb2579cc3b8501c223ddb 100644 (file)
@@ -189,8 +189,11 @@ $string['cancelled'] = 'Cancelled';
 $string['categories'] = 'Course categories';
 $string['category'] = 'Category';
 $string['categoryadded'] = 'The category \'$a\' was added';
+$string['categorycurrentcontents'] = 'Contents of $a';
+$string['categorycontents'] = 'Subcategories and courses';
 $string['categorydeleted'] = 'The category \'$a\' was deleted';
 $string['categoryduplicate'] = 'A category named \'$a\' already exists!';
+$string['categorymodifiedcancel'] = 'Category was modified! Please cancel and try again.';
 $string['categoryname'] = 'Category name';
 $string['categoryupdated'] = 'The category \'$a\' was updated';
 $string['changedpassword'] = 'Changed password';
@@ -365,7 +368,9 @@ $string['delete'] = 'Delete';
 $string['deleteall'] = 'Delete all';
 $string['deleteallcomments'] = 'Delete all comments';
 $string['deleteallratings'] = 'Delete all ratings';
+$string['deletecategory'] = 'Delete category: $a';
 $string['deletecategorycheck'] = 'Are you absolutely sure you want to completely delete this category <b>\'$a\'</b>?<br />This will move all courses into the parent category if there is one, or into Miscellaneous.';
+$string['deletecategorycheck2'] = 'If you delete this category, you need to choose what to do with the courses and subcategories it contains.';
 $string['deletecheck'] = 'Delete $a ?';
 $string['deletecheckfiles'] = 'Are you absolutely sure you want to delete these files?';
 $string['deletecheckfull'] = 'Are you absolutely sure you want to completely delete $a ?';
@@ -963,6 +968,7 @@ $string['moreprofileinfoneeded'] = 'Please tell us more about yourself';
 $string['mostrecently'] = 'most recently';
 $string['move'] = 'Move';
 $string['movecategoryto'] = 'Move category to:';
+$string['movecategorycontentto'] = 'Move into';
 $string['movecourseto'] = 'Move course to:';
 $string['movedown'] = 'Move down';
 $string['movefilestohere'] = 'Move files to here';
index 7f8b04982721e2333432fa139b10353f05f00cc2..3cd0232d9a3084cd00be16b0b940e04e4c922f26 100644 (file)
@@ -26,10 +26,12 @@ $string['created'] = 'Created';
 $string['createdmodifiedheader'] = 'Created / Last Saved';
 $string['defaultfor'] = 'Default for $a';
 $string['defaultinfofor'] = 'The default category for questions shared in context \'$a\'.';
+$string['deletecoursecategorywithquestions'] = 'There are questions in the question bank associated with this course category. If you proceed, they will be deleted. You may wish to move them first, using the question bank interface.';
 $string['donothing']= 'Don\'t copy or move files or change links.';
 $string['editingcategory'] = 'Editing a category';
 $string['editingquestion'] = 'Editing a question';
 $string['erroraccessingcontext'] = 'Cannot access context';
+$string['errordeletingquestionsfromcategory'] = 'Error deleting questions from category $a.';
 $string['errorfilecannotbecopied'] = 'Error cannot copy file $a.';
 $string['errorfilecannotbemoved'] = 'Error cannot move file $a.';
 $string['errormovingquestions'] = 'Error while moving questions with ids $a.';
@@ -42,6 +44,7 @@ $string['fractionsnomax'] = 'One of the answers should have a score of 100%% so
 $string['getcategoryfromfile'] = 'Get category from file';
 $string['getcontextfromfile'] = 'Get context from file';
 $string['ignorebroken'] = 'Ignore broken links';
+$string['invalidcontextinhasanyquestions'] = 'Invalid context passed to question_context_has_any_questions.';
 $string['linkedfiledoesntexist'] = 'Linked file $a doesn\'t exist';
 $string['makechildof'] = "Make Child of '\$a'";
 $string['maketoplevelitem'] = 'Move to top level';
@@ -49,6 +52,7 @@ $string['missingimportantcode'] = 'This question type is missing important code:
 $string['modified'] = 'Last saved';
 $string['move']= 'Move from $a and change links.';
 $string['movecategory']= 'Move Category';
+$string['movedquestionsandcategories'] = 'Moved questions and question categories from $a->oldplace to $a->newplace.';
 $string['movelinksonly']= 'Just change where links point to, do not move or copy files.';
 $string['moveqtoanothercontext']= 'Move question to another context.';
 $string['moveq']= 'Move question(s)';
@@ -70,8 +74,12 @@ $string['permissionto'] = 'You have permission to :';
 $string['published'] = 'shared';
 $string['questionaffected'] = '<a href=\"$a->qurl\">Question \"$a->name\" ($a->qtype)</a> is in this question category but is also being used in <a href=\"$a->qurl\">quiz \"$a->quizname\"</a> in another course \"$a->coursename\".';
 $string['questionbank'] = 'Question bank';
+$string['questioncategory'] = 'Question category';
 $string['questioncatsfor'] = 'Question Categories for \'$a\'';
 $string['questiondoesnotexist'] = 'This question does not exist';
+$string['questionsmovedto'] = 'Questions still in use moved to "$a" in the parent course category.';
+$string['questionsrescuedfrom'] = 'Questions saved from context $a.';
+$string['questionsrescuedfrominfo'] = 'These questions (some of which may be hidden) where saved when context $a was deleted because they are still used by some quizzes or other activities.';
 $string['questionuse'] = 'Use question in this activity';
 $string['shareincontext'] = 'Share in context for $a';
 $string['tofilecategory'] = 'Write category to file';
index 3987cd6b3a77d5b6fea524e36fceeabd7ad2b0a7..5607b7dc7e3ac1d034993c63366bb363b7f96d47 100644 (file)
@@ -1186,7 +1186,7 @@ function remove_grade_letters($context, $showfeedback) {
 /**
  * Remove all grade related course data - history is kept
  * @param int $courseid
- * @param bool @showfeedback print feedback
+ * @param bool $showfeedback print feedback
  */
 function remove_course_grades($courseid, $showfeedback) {
     $strdeleted = get_string('deleted');
@@ -1222,6 +1222,17 @@ function remove_course_grades($courseid, $showfeedback) {
     }
 }
 
+/**
+ * Called when course category deleted - cleanup gradebook
+ * @param int $categoryid course category id
+ * @param int $newparentid empty means everything deleted, otherwise id of category where content moved
+ * @param bool $showfeedback print feedback
+ */
+function grade_course_category_delete($categoryid, $newparentid, $showfeedback) {
+    $context = get_context_instance(CONTEXT_COURSECAT, $categoryid);
+    delete_records('grade_letters', 'contextid', $context->id);
+}
+
 /**
  * Does gradebook cleanup when module uninstalled.
  */
index bcd0f5829c49c738bfa0466b30d7f5ba47f2a52a..d2a7f00883ee9d226c340c418b71760284831ae5 100644 (file)
@@ -265,6 +265,29 @@ function question_list_instances($questionid) {
     return $instances;
 }
 
+/**
+ * Determine whether there arey any questions belonging to this context, that is whether any of its
+ * question categories contain any questions. This will return true even if all the questions are
+ * hidden.
+ *
+ * @param mixed $context either a context object, or a context id.
+ * @return boolean whether any of the question categories beloning to this context have
+ *         any questions in them.
+ */
+function question_context_has_any_questions($context) {
+    global $CFG;
+    if (is_object($context)) {
+        $contextid = $context->id;
+    } else if (is_numeric($context)) {
+        $contextid = $context;
+    } else {
+        print_error('invalidcontextinhasanyquestions', 'question');
+    }
+    return record_exists_sql('SELECT * FROM ' . $CFG->prefix . 'question q ' .
+            'JOIN ' . $CFG->prefix . 'question_categories qc ON qc.id = q.category ' .
+            "WHERE qc.contextid = $contextid AND q.parent = 0");
+}
+
 /**
  * Returns list of 'allowed' grades for grade selection
  * formatted suitably for dropdown box function
@@ -518,6 +541,116 @@ function question_delete_course($course, $feedback=true) {
     return true;
 }
 
+/**
+ * Category is about to be deleted,
+ * 1/ All question categories and their questions are deleted for this course category.
+ * 2/ All questions are moved to new category
+ *
+ * @param object $category course category object
+ * @param object $newcategory empty means everything deleted, otherwise id of category where content moved
+ * @param boolean $feedback to specify if the process must output a summary of its work
+ * @return boolean
+ */
+function question_delete_course_category($category, $newcategory, $feedback=true) {
+    $context = get_context_instance(CONTEXT_COURSECAT, $category->id);
+    if (empty($newcategory)) {
+        $feedbackdata   = array(); // To store feedback to be showed at the end of the process
+        $rescueqcategory = null; // See the code around the call to question_save_from_deletion.
+        $strcatdeleted = get_string('unusedcategorydeleted', 'quiz');
+
+        // Loop over question categories.
+        if ($categories = get_records('question_categories', 'contextid', $context->id, 'parent', 'id, parent, name')) {
+            foreach ($categories as $category) {
+    
+                // Deal with any questions in the category.
+                if ($questions = get_records('question', 'category', $category->id)) {
+
+                    // Try to delete each question.
+                    foreach ($questions as $question) {
+                        delete_question($question->id);
+                    }
+
+                    // Check to see if there were any questions that were kept because they are
+                    // still in use somehow, even though quizzes in courses in this category will
+                    // already have been deteted. This could happen, for example, if questions are
+                    // added to a course, and then that course is moved to another category (MDL-14802).
+                    $questionids = get_records_select_menu('question', 'category = ' . $category->id, '', 'id,1');
+                    if (!empty($questionids)) {
+                        if (!$rescueqcategory = question_save_from_deletion(implode(',', array_keys($questionids)),
+                                get_parent_contextid($context), print_context_name($context), $rescueqcategory)) {
+                            return false;
+                       }
+                       $feedbackdata[] = array($category->name, get_string('questionsmovedto', 'question', $rescueqcategory->name));
+                    }
+                }
+
+                // Now delete the category.
+                if (!delete_records('question_categories', 'id', $category->id)) {
+                    return false;
+                }
+                $feedbackdata[] = array($category->name, $strcatdeleted);
+
+            } // End loop over categories.
+        }
+
+        // Output feedback if requested.
+        if ($feedback and $feedbackdata) {
+            $table = new stdClass;
+            $table->head = array(get_string('questioncategory','question'), get_string('action'));
+            $table->data = $feedbackdata;
+            print_table($table);
+        }
+
+    } else {
+        // Move question categories ot the new context.
+        if (!$newcontext = get_context_instance(CONTEXT_COURSECAT, $newcategory->id)) {
+            return false;
+        }
+        if (!set_field('question_categories', 'contextid', $newcontext->id, 'contextid', $context->id)) {
+            return false;
+        }
+        if ($feedback) {
+            $a = new stdClass;
+            $a->oldplace = print_context_name($context);
+            $a->newplace = print_context_name($newcontext);
+            notify(get_string('movedquestionsandcategories', 'question', $a), 'notifysuccess');
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Enter description here...
+ *
+ * @param string $questionids list of questionids
+ * @param object $newcontext the context to create the saved category in.
+ * @param string $oldplace a textual description of the think being deleted, e.g. from get_context_name 
+ * @param object $newcategory
+ * @return mixed false on 
+ */
+function question_save_from_deletion($questionids, $newcontextid, $oldplace, $newcategory = null) {
+    // Make a category in the parent context to move the questions to.
+    if (is_null($newcategory)) {
+        $newcategory = new object();
+        $newcategory->parent = 0;
+        $newcategory->contextid = $newcontextid;
+        $newcategory->name = addslashes(get_string('questionsrescuedfrom', 'question', $oldplace));
+        $newcategory->info = addslashes(get_string('questionsrescuedfrominfo', 'question', $oldplace));
+        $newcategory->sortorder = 999;
+        $newcategory->stamp = make_unique_id_code();
+        if (!$newcategory->id = insert_record('question_categories', $newcategory)) {
+            return false;
+        }
+    }
+
+    // Move any remaining questions to the 'saved' category.
+    if (!question_move_questions_to_category($questionids, $newcategory->id)) {
+        return false;
+    }
+    return $newcategory;
+}
+
 /**
  * All question categories and their questions are deleted for this activity.
  *