### Eclipse Workspace Patch 1.0
#P moodle19
Index: lang/en_utf8/error.php
===================================================================
RCS file: /cvsroot/moodle/moodle/lang/en_utf8/error.php,v
retrieving revision 1.26.2.7
diff -u -r1.26.2.7 error.php
--- lang/en_utf8/error.php	29 Feb 2008 06:36:27 -0000	1.26.2.7
+++ lang/en_utf8/error.php	28 Apr 2008 21:51:43 -0000
@@ -72,6 +72,7 @@
 $string['modulemissingcode'] = 'Module $a is missing the code needed to perform this function';
 $string['modulerequirementsnotmet'] = 'Module \"$a->modulename\" ($a->moduleversion) could not be installed.  It requires a newer version of Moodle (currently you are using $a->currentmoodle, you need $a->requiremoodle).';
 $string['mustbeteacher'] = 'You must be a teacher to look at this page';
+$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['nologinas'] = 'You are not allowed to login as that user';
Index: lang/en_utf8/moodle.php
===================================================================
RCS file: /cvsroot/moodle/moodle/lang/en_utf8/moodle.php,v
retrieving revision 1.141.2.35
diff -u -r1.141.2.35 moodle.php
--- lang/en_utf8/moodle.php	20 Apr 2008 01:15:54 -0000	1.141.2.35
+++ lang/en_utf8/moodle.php	28 Apr 2008 21:51:45 -0000
@@ -188,8 +188,10 @@
 $string['categories'] = 'Course categories';
 $string['category'] = 'Category';
 $string['categoryadded'] = 'The category \'$a\' was added';
+$string['categorycontent'] = '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';
@@ -364,7 +366,9 @@
 $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'] = 'Are you absolutely sure you want to completely delete this category?<br />You can either delete all subcategories and courses or move them into existing category.<br />';
 $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 ?';
@@ -959,6 +963,7 @@
 $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: course/editcategory.php
===================================================================
RCS file: /cvsroot/moodle/moodle/course/editcategory.php,v
retrieving revision 1.3.2.4
diff -u -r1.3.2.4 editcategory.php
--- course/editcategory.php	24 Apr 2008 08:35:47 -0000	1.3.2.4
+++ course/editcategory.php	28 Apr 2008 21:51:39 -0000
@@ -72,7 +72,7 @@
         } 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: course/lib.php
===================================================================
RCS file: /cvsroot/moodle/moodle/course/lib.php,v
retrieving revision 1.538.2.37
diff -u -r1.538.2.37 lib.php
--- course/lib.php	25 Apr 2008 14:01:23 -0000	1.538.2.37
+++ course/lib.php	28 Apr 2008 21:51:43 -0000
@@ -2679,6 +2679,97 @@
     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);
+    question_delete_course_category($category, 0, $showfeedback);
+
+    // 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);
+    question_delete_course_category($category, $newparentcat, $showfeedback);
+
+    // 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: course/index.php
===================================================================
RCS file: /cvsroot/moodle/moodle/course/index.php,v
retrieving revision 1.89.2.3
diff -u -r1.89.2.3 index.php
--- course/index.php	9 Jan 2008 10:56:48 -0000	1.89.2.3
+++ course/index.php	28 Apr 2008 21:51:39 -0000
@@ -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');
         }
@@ -95,67 +94,54 @@
 
 /// 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)) {
-                            error('Could not update a child category!', '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)) {
-                                error('Could not update a child course!', '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");
+        } else if (!$data= $mform->get_data(false)) {
+            print_category_edit_header();
+            print_heading($heading);
+            print_box(get_string('deletecategorycheck2'), 'generalbox boxwidthnormal boxaligncenter');
+            $mform->display();
+            print_footer();
+            exit();
+        }
 
-                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!
@@ -289,7 +275,7 @@
     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)) {
@@ -298,7 +284,7 @@
             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 +414,17 @@
         }
     }
 }
+
+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: lib/questionlib.php
===================================================================
RCS file: /cvsroot/moodle/moodle/lib/questionlib.php,v
retrieving revision 1.119.2.5
diff -u -r1.119.2.5 questionlib.php
--- lib/questionlib.php	28 Feb 2008 12:52:59 -0000	1.119.2.5
+++ lib/questionlib.php	28 Apr 2008 21:51:48 -0000
@@ -515,6 +515,45 @@
 }
 
 /**
+ * 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($newparentid)) {
+        //TODO: add some minimal feedback
+        if ($categories = get_records('question_categories', 'contextid', $context->id, 'parent', 'id, parent, name')) {
+            foreach ($categories as $category) {
+    
+                //Delete it completely (questions and category itself)
+                //deleting questions
+                if ($questions = get_records("question", "category", $category->id)) {
+                    foreach ($questions as $question) {
+                        delete_question($question->id);
+                    }
+                    delete_records("question", "category", $category->id);
+                }
+                //delete the category
+                delete_records('question_categories', 'id', $category->id);
+            }
+        }
+        return true;
+
+    } else {
+        if (!$newcontext = get_context_instance(CONTEXT_COURSECAT, $newcategory->id)) {
+            return false;
+        }
+        return set_field('question_categories', 'contextid', $newcontext->id, 'contextid', $context->id);
+    }
+}
+
+/**
  * All question categories and their questions are deleted for this activity.
  *
  * @param object $cm the course module object representing the activity
Index: lib/gradelib.php
===================================================================
RCS file: /cvsroot/moodle/moodle/lib/gradelib.php,v
retrieving revision 1.120.2.25
diff -u -r1.120.2.25 gradelib.php
--- lib/gradelib.php	16 Mar 2008 23:21:35 -0000	1.120.2.25
+++ lib/gradelib.php	28 Apr 2008 21:51:47 -0000
@@ -1186,7 +1186,7 @@
 /**
  * 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');
@@ -1223,6 +1223,17 @@
 }
 
 /**
+ * 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.
  */
 function grade_uninstalled_module($modname) {
Index: course/delete_category_form.php
===================================================================
RCS file: course/delete_category_form.php
diff -N course/delete_category_form.php
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ course/delete_category_form.php	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,99 @@
+<?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('delete'));
+
+        $mform->addElement('static', 'categorylabel', get_string('category'), 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('categorycontent'), $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;
+    }
+}
+?>
