MDL-14378 When deleting course category deal with everything that depends on its context; merged from MOODLE_19_STABLE

This commit is contained in:
skodak 2008-05-13 21:52:38 +00:00
parent 6a441999d0
commit e2b347e933
9 changed files with 418 additions and 61 deletions

View File

@ -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;
}
}
?>

View 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;

View 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');
}
@ -95,67 +94,59 @@
/// From now on is all the admin/course creator functions
/// Print headings
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());
}
print_heading($strcategories);
/// Delete a category if necessary
if (!empty($delete) and confirm_sesskey()) {
require_once('delete_category_form.php');
// 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');
}
}
}
/// 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');
}
}
}
}
/// 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();
}
if (!$deletecat = get_record('course_categories', 'id', $delete)) {
error('Incorrect category id', 'index.php');
}
$heading = get_string('deletecategory', '', format_string($deletecat->name));
$context = get_context_instance(CONTEXT_COURSECAT, $delete);
require_capability('moodle/category:delete', $context);
$mform = new delete_category_form(null, $deletecat);
$mform->set_data(array('delete'=>$delete));
if ($mform->is_cancelled()) {
redirect('index.php');
} 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!
@ -289,7 +280,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 +289,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 +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());
}
}
?>

View 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.

View 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!';

View 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';

View 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';

View 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.
*/

View 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.
*