From 0cbe81112a30a0f0654cca8a62ed458352a8f8bc Mon Sep 17 00:00:00 2001 From: skodak <skodak> Date: Mon, 16 Jun 2008 14:25:53 +0000 Subject: [PATCH] MDL-14580 rewritten course sorting algorithms --- admin/cliupgrade.php | 14 +- admin/index.php | 11 +- backup/restorelib.php | 41 +--- course/category.php | 48 ++--- course/editcategory.php | 8 +- course/index.php | 56 ++---- course/lib.php | 71 +++---- course/pending.php | 14 +- enrol/database/enrol.php | 19 +- enrol/imsenterprise/enrol.php | 11 +- enrol/ldap/enrol.php | 23 +-- lib/accesslib.php | 17 ++ lib/datalib.php | 353 ++++++++++++++++++++-------------- 13 files changed, 313 insertions(+), 373 deletions(-) diff --git a/admin/cliupgrade.php b/admin/cliupgrade.php index b39ea248f2..44d86dc660 100644 --- a/admin/cliupgrade.php +++ b/admin/cliupgrade.php @@ -1166,17 +1166,9 @@ if ( file_exists(dirname(dirname(__FILE__)) . '/config.php')) { $page = page_create_object(PAGE_COURSE_VIEW, $newid); blocks_repopulate_page($page); // Return value not checked because you can always edit later - $cat = new Object(); - $cat->name = get_string('miscellaneous'); - $cat->depth = 1; - if ($catid = $DB->insert_record('course_categories', $cat)) { - // make sure category context exists - get_context_instance(CONTEXT_COURSECAT, $catid); - mark_context_dirty('/'.SYSCONTEXTID); - // do nothing - } else { - print_error('cannotsetupcategory', 'error'); - } + // create default course category + $cat = get_course_category(); + } else { print_error('cannotsetupsite', 'error'); } diff --git a/admin/index.php b/admin/index.php index d13fc11c9f..125bebbbf6 100644 --- a/admin/index.php +++ b/admin/index.php @@ -466,15 +466,8 @@ $page = page_create_object(PAGE_COURSE_VIEW, $newid); blocks_repopulate_page($page); // Return value not checked because you can always edit later - $cat = new object(); - $cat->name = get_string('miscellaneous'); - $cat->depth = 1; - if (!$catid = $DB->insert_record('course_categories', $cat)) { - print_error('cannotsetupcategory', 'error'); - } - // make sure category context exists - get_context_instance(CONTEXT_COURSECAT, $catid); - mark_context_dirty('/'.SYSCONTEXTID); + // create default course category + $cat = get_course_category(); redirect('index.php'); } diff --git a/backup/restorelib.php b/backup/restorelib.php index af3e7d644f..a166b9adff 100644 --- a/backup/restorelib.php +++ b/backup/restorelib.php @@ -651,34 +651,13 @@ define('RESTORE_GROUPS_GROUPINGS', 3); $category = $DB->get_record("course_categories", array("name"=>$course_header->category->name)); } - //If no exists, get category id 1 + //If no exists, get default category if (!$category) { - $category = $DB->get_record("course_categories", array("id"=>"1")); + $category = get_course_category(); } - //If category 1 doesn'exists, lets create the course category (get it from backup file) - if (!$category) { - $ins_category = new object(); - $ins_category->name = $course_header->category->name; - $ins_category->parent = 0; - $ins_category->sortorder = 0; - $ins_category->coursecount = 0; - $ins_category->visible = 0; //To avoid interferences with the rest of the site - $ins_category->timemodified = time(); - $newid = $DB->insert_record("course_categories",$ins_category); - $category->id = $newid; - $category->name = $course_header->category->name; - } - //If exists, put new category id - if ($category) { - $course_header->category->id = $category->id; - $course_header->category->name = $category->name; - //Error, cannot locate category - } else { - $course_header->category->id = 0; - $course_header->category->name = get_string("unknowncategory"); - $status = false; - } + $course_header->category->id = $category->id; + $course_header->category->name = $category->name; //Create the course_object if ($status) { @@ -737,16 +716,8 @@ define('RESTORE_GROUPS_GROUPINGS', 3); $course->enrolenddate += $restore->course_startdateoffset; } $course->enrolperiod = $course_header->course_enrolperiod; - //Calculate sortorder field - $sortmax = $DB->get_record_sql('SELECT MAX(sortorder) AS max - FROM {course} - WHERE category=?', array($course->category)); - if (!empty($sortmax->max)) { - $course->sortorder = $sortmax->max + 1; - unset($sortmax); - } else { - $course->sortorder = 100; - } + //Put as last course in category + $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - 1; //Now, recode some languages (Moodle 1.5) if ($course->lang == 'ma_nt') { diff --git a/course/category.php b/course/category.php index 423f64f9e2..61f1c897be 100644 --- a/course/category.php +++ b/course/category.php @@ -79,16 +79,12 @@ if ($resort and confirm_sesskey()) { if ($courses = get_courses($category->id, "fullname ASC", 'c.id,c.fullname,c.sortorder')) { - // move it off the range - $count = $DB->get_record_sql('SELECT MAX(sortorder) AS max, 1 FROM {course} WHERE category= ?', array($category->id)); - $count = $count->max + 100; - $DB->begin_sql(); + $i = 1; foreach ($courses as $course) { - $DB->set_field('course', 'sortorder', $count, array('id'=>$course->id)); - $count++; + $DB->set_field('course', 'sortorder', $category->sortorder+$i, array('id'=>$course->id)); + $i++; } - $DB->commit_sql(); - fix_course_sortorder($category->id); + fix_course_sortorder(); // should not be needed } } } @@ -205,36 +201,24 @@ if ((!empty($moveup) or !empty($movedown)) and confirm_sesskey()) { require_capability('moodle/category:update', $context); - $movecourse = NULL; - $swapcourse = NULL; - - // ensure the course order has no gaps - // and isn't at 0 - fix_course_sortorder($category->id); - // we are going to need to know the range - $max = $DB->get_record_sql('SELECT MAX(sortorder) AS max, 1 FROM {course} WHERE category=?', array($category->id)); - $max = $max->max + 100; + // ensure the course order has continuous ordering + fix_course_sortorder(); + $swapcourse = NULL; if (!empty($moveup)) { - $movecourse = $DB->get_record('course', array('id'=>$moveup)); - $swapcourse = $DB->get_record('course', array('category'=>$category->id, 'sortorder'=>($movecourse->sortorder-1))); + if ($movecourse = $DB->get_record('course', array('id'=>$moveup))) { + $swapcourse = $DB->get_record('course', array('sortorder'=>$movecourse->sortorder-1)); + } } else { - $movecourse = $DB->get_record('course', array('id'=>$movedown)); - $swapcourse = $DB->get_record('course', array('category'=>$category->id, 'sortorder'=>($movecourse->sortorder+1))); - } - - if ($swapcourse and $movecourse) { // Renumber everything for robustness - $DB->begin_sql(); - if (!( $DB->set_field("course", "sortorder", $max, array("id"=>$swapcourse->id)) - && $DB->set_field("course", "sortorder", $swapcourse->sortorder, array("id"=>$movecourse->id)) - && $DB->set_field("course", "sortorder", $movecourse->sortorder, array("id"=>$swapcourse->id)) - )) { - notify("Could not update that course!"); + if ($movecourse = $DB->get_record('course', array('id'=>$movedown))) { + $swapcourse = $DB->get_record('course', array('sortorder'=>$movecourse->sortorder+1)); } - $DB->commit_sql(); } - + if ($swapcourse and $movecourse) { + $DB->set_field('course', 'sortorder', $swapcourse->sortorder, array('id'=>$movecourse->id)); + $DB->set_field('course', 'sortorder', $movecourse->sortorder, array('id'=>$swapcourse->id)); + } } } // End of editing stuff diff --git a/course/editcategory.php b/course/editcategory.php index 64970cdc7c..c3390eb196 100644 --- a/course/editcategory.php +++ b/course/editcategory.php @@ -58,7 +58,6 @@ if ($mform->is_cancelled()){ $newcategory = new stdClass(); $newcategory->name = $data->name; $newcategory->description = $data->description; - $newcategory->sortorder = 999; $newcategory->parent = $data->parent; // if $id = 0, the new category will be a top-level category if (!empty($data->theme) && !empty($CFG->allowcategorythemes)) { @@ -66,12 +65,14 @@ if ($mform->is_cancelled()){ theme_setup(); } - if (empty($category) && has_capability('moodle/category:create', $context)) { // Create a new category + if (empty($category) && has_capability('moodle/category:create', $context)) { // Create a new category + $newcategory->sortorder = MAX_COURSES_IN_CATEGORY*MAX_COURSE_CATEGORIES; // put as last category in any parent cat if (!$newcategory->id = $DB->insert_record('course_categories', $newcategory)) { notify( "Could not insert the new category '$newcategory->name' "); } else { $newcategory->context = get_context_instance(CONTEXT_COURSECAT, $newcategory->id); mark_context_dirty($newcategory->context->path); + fix_course_sortorder(); redirect('index.php?categoryedit=on'); } } elseif (has_capability('moodle/category:update', $context)) { @@ -79,7 +80,7 @@ if ($mform->is_cancelled()){ if ($newcategory->parent != $category->parent) { $parent_cat = $DB->get_record('course_categories', array('id'=>$newcategory->parent)); - move_category($newcategory, $parent_cat); + move_category($newcategory, $parent_cat); // includes sortorder fix } if (!$DB->update_record('course_categories', $newcategory)) { @@ -90,7 +91,6 @@ if ($mform->is_cancelled()){ } else { $redirect_link = 'category.php?id='.$newcategory->id.'&categoryedit=on'; } - fix_course_sortorder(); redirect($redirect_link); } } diff --git a/course/index.php b/course/index.php index db49319498..f77b1e15c9 100644 --- a/course/index.php +++ b/course/index.php @@ -199,61 +199,33 @@ if ((!empty($moveup) or !empty($movedown)) and confirm_sesskey()) { + fix_course_sortorder(); $swapcategory = NULL; - $movecategory = NULL; if (!empty($moveup)) { if ($movecategory = $DB->get_record('course_categories', array('id'=>$moveup))) { - $categories = get_categories($movecategory->parent); - - foreach ($categories as $category) { - if ($category->id == $movecategory->id) { - break; - } - $swapcategory = $category; + if ($swapcategory = $DB->get_records_select('course_categories', "sortorder<? AND parent=?", array($movecategory->sortorder, $movecategory->parent), 'sortorder ASC', '*', 0, 1)) { + $swapcategory = reset($swapcategory); } - unset($category); } - } - if (!empty($movedown)) { + } else { if ($movecategory = $DB->get_record('course_categories', array('id'=>$movedown))) { - $categories = get_categories($movecategory->parent); - - $choosenext = false; - foreach ($categories as $category) { - if ($choosenext) { - $swapcategory = $category; - break; - } - if ($category->id == $movecategory->id) { - $choosenext = true; - } + if ($swapcategory = $DB->get_records_select('course_categories', "sortorder>? AND parent=?", array($movecategory->sortorder, $movecategory->parent), 'sortorder ASC', '*', 0, 1)) { + $swapcategory = reset($swapcategory); } - unset($category); } } - if ($swapcategory and $movecategory) { // Renumber everything for robustness - $count=0; - foreach ($categories as $category) { - $count++; - if ($category->id == $swapcategory->id) { - $category = $movecategory; - } else if ($category->id == $movecategory->id) { - $category = $swapcategory; - } - if (!$DB->set_field('course_categories', 'sortorder', $count, array('id'=>$category->id))) { - notify('Could not update that category!'); - } - } - unset($category); + if ($swapcategory and $movecategory) { + $DB->set_field('course_categories', 'sortorder', $swapcategory->sortorder, array('id'=>$movecategory->id)); + $DB->set_field('course_categories', 'sortorder', $movecategory->sortorder, array('id'=>$swapcategory->id)); } - } -/// Find any orphan courses that don't yet have a valid category and set to default - fix_coursecategory_orphans(); + // finally reorder courses + fix_course_sortorder(); + } -/// Should be a no-op 99% of the cases - fix_course_sortorder(); +/// This should not be needed anymore + //fix_course_sortorder(); /// Print out the categories with all the knobs diff --git a/course/lib.php b/course/lib.php index 84dcf1adbf..562f9863dc 100644 --- a/course/lib.php +++ b/course/lib.php @@ -2854,49 +2854,28 @@ function category_delete_move($category, $newparentid, $showfeedback=true) { function move_courses($courseids, $categoryid) { global $CFG, $DB; - if (!empty($courseids)) { + if (!empty($courseids) and $category = $DB->get_record('course_categories', array('id'=>$categoryid))) { + $courseids = array_reverse($courseids); + $i = 1; - $courseids = array_reverse($courseids); - - foreach ($courseids as $courseid) { - - if (! $course = $DB->get_record("course", array("id"=>$courseid))) { - notify("Error finding course $courseid"); - } else { - // figure out a sortorder that we can use in the destination category - $sortorder = $DB->get_field_sql('SELECT MIN(sortorder)-1 AS min - FROM {course} WHERE category=?', array($categoryid)); - if (is_null($sortorder) || $sortorder === false) { - // the category is empty - // rather than let the db default to 0 - // set it to > 100 and avoid extra work in fix_coursesortorder() - $sortorder = 200; - } else if ($sortorder < 10) { - fix_course_sortorder($categoryid); - } - - $course->category = $categoryid; - $course->sortorder = $sortorder; - $course->fullname = $course->fullname; - $course->shortname = $course->shortname; - $course->summary = $course->summary; - $course->password = $course->password; - $course->teacher = $course->teacher; - $course->teachers = $course->teachers; - $course->student = $course->student; - $course->students = $course->students; - - if (!$DB->update_record('course', $course)) { - notify("An error occurred - course not moved!"); - } + foreach ($courseids as $courseid) { + if (!$course = $DB->get_record("course", array("id"=>$courseid))) { + notify("Error finding course $courseid"); + } else { + $course->category = $categoryid; + $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++; - $context = get_context_instance(CONTEXT_COURSE, $course->id); - $newparent = get_context_instance(CONTEXT_COURSECAT, $course->category); - context_moved($context, $newparent); + if (!$DB->update_record('course', $course)) { + notify("An error occurred - course not moved!"); } + + $context = get_context_instance(CONTEXT_COURSE, $course->id); + $newparent = get_context_instance(CONTEXT_COURSECAT, $course->category); + context_moved($context, $newparent); } - fix_course_sortorder(); } + fix_course_sortorder(); + } return true; } @@ -2913,7 +2892,9 @@ function move_category ($category, $newparentcat) { if (!$DB->set_field('course_categories', 'parent', 0, array('id'=>$category->id))) { return false; } + $newparent = get_context_instance(CONTEXT_SYSTEM); + } else { if (!$DB->set_field('course_categories', 'parent', $newparentcat->id, array('id'=>$category->id))) { return false; @@ -2923,8 +2904,10 @@ function move_category ($category, $newparentcat) { context_moved($context, $newparent); - // The most effective thing would be to find the common parent, - // until then, do it sitewide... + // now make it last in new category + $DB->set_field('course_categories', 'sortorder', MAX_COURSES_IN_CATEGORY*MAX_COURSE_CATEGORIES, array('id'=>$category->id)); + + // and fix the sortorders fix_course_sortorder(); return true; @@ -2990,12 +2973,8 @@ function create_course($data) { $data->timecreated = time(); - // place at beginning of category - fix_course_sortorder(); - $data->sortorder = $DB->get_field_sql("SELECT MIN(sortorder)-1 FROM {course} WHERE category=?", array($data->category)); - if (empty($data->sortorder)) { - $data->sortorder = 100; - } + // place at beginning of any category + $data->sortorder = 0; if ($newcourseid = $DB->insert_record('course', $data)) { // Set up new course diff --git a/course/pending.php b/course/pending.php index 7564f7a541..11de7e4e6a 100644 --- a/course/pending.php +++ b/course/pending.php @@ -17,19 +17,10 @@ if (!empty($approve) and confirm_sesskey()) { if ($course = $DB->get_record("course_request", array("id"=>$approve))) { - // place at beginning of category - fix_course_sortorder(); - - if (empty($CFG->defaultrequestcategory) or !$DB->record_exists('course_categories', array('id'=>$CFG->defaultrequestcategory))) { - // default to first top level directory, hacky but means things don't break - $CFG->defaultrequestcategory = $DB->get_field('course_categories', 'id', array('parent'=>'0')); - } + $category = get_course_category($CFG->defaultrequestcategory); $course->category = $CFG->defaultrequestcategory; - $course->sortorder = $DB->get_field_sql("SELECT min(sortorder)-1 FROM {course} WHERE category=?", array($course->category)); - if (empty($course->sortorder)) { - $course->sortorder = 1000; - } + $course->sortorder = $category->sortorder; // place as the first in category $course->requested = 1; unset($course->reason); unset($course->id); @@ -54,6 +45,7 @@ } $DB->delete_records('course_request', array('id'=>$approve)); $success = 1; + fix_course_sortorder(); } if (!empty($success)) { $user = $DB->get_record('user', array('id'=>$teacherid)); diff --git a/enrol/database/enrol.php b/enrol/database/enrol.php index f848a0ff6e..3cd6acf15f 100644 --- a/enrol/database/enrol.php +++ b/enrol/database/enrol.php @@ -554,7 +554,7 @@ function create_course ($course,$skip_fix_course_sortorder=0){ global $CFG, $DB; // define a template - if(!empty($CFG->enrol_db_template)){ + if (!empty($CFG->enrol_db_template)){ $template = $DB->get_record("course", array('shortname'=>$CFG->enrol_db_template)); $template = (array)$template; } else { @@ -585,14 +585,10 @@ function create_course ($course,$skip_fix_course_sortorder=0){ } } - $course->category = 1; // the misc 'catch-all' category - if (!empty($CFG->enrol_db_category)){ //category = 0 or undef will break moodle - $course->category = $CFG->enrol_db_category; - } + $category = get_course_category($CFG->enrol_db_category); - // define the sortorder - $sort = $DB->get_field_sql('SELECT COALESCE(MAX(sortorder)+1, 100) AS max FROM {course} WHERE category= ?', array($course->category)); - $course->sortorder = $sort; + // put at the end of category + $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - 1; // override with local data $course->startdate = time() + 3600 * 24; @@ -608,15 +604,14 @@ function create_course ($course,$skip_fix_course_sortorder=0){ // store it and log if ($newcourseid = $DB->insert_record("course", $course)) { // Set up new course - $section = NULL; - $section->course = $newcourseid; // Create a default section. + $section = new object(); + $section->course = $newcourseid; // Create a default section. $section->section = 0; $section->id = $DB->insert_record("course_sections", $section); $page = page_create_object(PAGE_COURSE_VIEW, $newcourseid); blocks_repopulate_page($page); // Return value no - - if(!$skip_fix_course_sortorder){ + if (!$skip_fix_course_sortorder){ fix_course_sortorder(); } add_to_log($newcourseid, "course", "new", "view.php?id=$newcourseid", "enrol/database auto-creation"); diff --git a/enrol/imsenterprise/enrol.php b/enrol/imsenterprise/enrol.php index 325dce251d..1c26cfc7dd 100644 --- a/enrol/imsenterprise/enrol.php +++ b/enrol/imsenterprise/enrol.php @@ -536,15 +536,8 @@ function process_group_tag($tagcontents){ $course->startdate = time(); $course->numsections = 1; // Choose a sort order that puts us at the start of the list! - $sortinfo = $DB->get_record_sql('SELECT MIN(sortorder) AS min, MAX(sortorder) AS max FROM {course} WHERE category<>0'); - if (is_object($sortinfo)) { // no courses? - $max = $sortinfo->max; - $min = $sortinfo->min; - unset($sortinfo); - $course->sortorder = $min - 1; - }else{ - $course->sortorder = 1000; - } + $course->sortorder = 0; + if($course->id = $DB->insert_record('course', $course)){ // Setup the blocks diff --git a/enrol/ldap/enrol.php b/enrol/ldap/enrol.php index 23c57d2d5f..bdab28a084 100755 --- a/enrol/ldap/enrol.php +++ b/enrol/ldap/enrol.php @@ -550,7 +550,7 @@ function create_course ($course_ext,$skip_fix_course_sortorder=0){ global $CFG, $DB; // override defaults with template course - if(!empty($CFG->enrol_ldap_template)){ + if (!empty($CFG->enrol_ldap_template)){ $course = $DB->get_record("course", array('shortname'=>$CFG->enrol_ldap_template)); unset($course->id); // so we are clear to reinsert the record unset($course->sortorder); @@ -580,19 +580,11 @@ function create_course ($course_ext,$skip_fix_course_sortorder=0){ $course->summary = empty($CFG->enrol_ldap_course_summary) || empty($course_ext[$CFG->enrol_ldap_course_summary][0]) ? '' : $course_ext[$CFG->enrol_ldap_course_summary][0]; - - if(!empty($CFG->enrol_ldap_category)){ // optional ... but ensure it is set! - $course->category = $CFG->enrol_ldap_category; - } - if ($course->category == 0){ // must be avoided as it'll break moodle - $course->category = 1; // the misc 'catch-all' category - } - // define the sortorder (yuck) - $sort = $DB->get_record_sql('SELECT MAX(sortorder) AS max, 1 FROM {course} WHERE category=?', array($course->category)); - $sort = $sort->max; - $sort++; - $course->sortorder = $sort; + $category = get_course_category($CFG->enrol_db_category); + + // put at the end of category + $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - 1; // override with local data $course->startdate = time(); @@ -602,14 +594,13 @@ function create_course ($course_ext,$skip_fix_course_sortorder=0){ // store it and log if ($newcourseid = $DB->insert_record("course", $course)) { // Set up new course $section = new object(); - $section->course = $newcourseid; // Create a default section. + $section->course = $newcourseid; // Create a default section. $section->section = 0; $section->id = $DB->insert_record("course_sections", $section); $page = page_create_object(PAGE_COURSE_VIEW, $newcourseid); blocks_repopulate_page($page); // Return value no - - if(!$skip_fix_course_sortorder){ + if (!$skip_fix_course_sortorder){ fix_course_sortorder(); } add_to_log($newcourseid, "course", "new", "view.php?id=$newcourseid", "enrol/ldap auto-creation"); diff --git a/lib/accesslib.php b/lib/accesslib.php index 4f6eeb5578..43ff108f83 100755 --- a/lib/accesslib.php +++ b/lib/accesslib.php @@ -5118,6 +5118,23 @@ function component_level_changed($cap, $comp, $contextlevel) { return ($cap->component != $comp || $cap->contextlevel != $contextlevel); } +/** + * Rebuild all related context depth and path caches + * @param array $fixcontexts array of contexts + */ +function rebuild_contexts(array $fixcontexts) { + global $DB; + + foreach ($fixcontexts as $context) { + if ($context->path) { + mark_context_dirty($context->path); + } + $DB->set_field_select('context', 'depth', 0, "path LIKE '%/$context->id/%'"); + $DB->set_field('context', 'depth', 0, array('id'=>$context->id)); + } + build_context_path(false); +} + /** * Populate context.path and context.depth where missing. * @param bool $force force a complete rebuild of the path and depth fields. diff --git a/lib/datalib.php b/lib/datalib.php index 8c1d76b3b5..e960298890 100644 --- a/lib/datalib.php +++ b/lib/datalib.php @@ -11,6 +11,9 @@ * @package moodlecore */ +define('MAX_COURSES_IN_CATEGORY', 10000); // MAX_COURSES_IN_CATEGORY * MAX_COURSE_CATEGORIES must not be more than max integer! +define('MAX_COURSE_CATEGORIES', 10000); + /** * Sets up global $DB moodle_database instance * @return void @@ -1272,185 +1275,243 @@ function get_all_subcategories($catid) { return $subcats; } - /** -* This recursive function makes sure that the courseorder is consecutive -* -* @param type description -* -* $n is the starting point, offered only for compatilibity -- will be ignored! -* $safe (bool) prevents it from assuming category-sortorder is unique, used to upgrade -* safely from 1.4 to 1.5 -*/ -function fix_course_sortorder($categoryid=0, $n=0, $safe=0, $depth=0, $path='') { - global $CFG, $DB; + * Return specified category, default if given does not exist + * @param int $catid course category id + * @return object caregory + */ +function get_course_category($catid=0) { + global $DB; + + $category = false; + + if (!empty($catid)) { + $category = $DB->get_record('course_categories', array('id'=>$catid)); + } - $count = 0; - - $catgap = 1000; // "standard" category gap - $tolerance = 200; // how "close" categories can get - - if ($categoryid > 0){ - // update depth and path - $cat = $DB->get_record('course_categories', array('id'=>$categoryid)); - if ($cat->parent == 0) { - $depth = 0; - $path = ''; - } else if ($depth == 0 ) { // doesn't make sense; get from DB - // this is only called if the $depth parameter looks dodgy - $parent = $DB->get_record('course_categories', array('id'=>$cat->parent)); - $path = $parent->path; - $depth = $parent->depth; + if (!$category) { + // the first category is considered default for now + if ($category = $DB->get_records('course_categories', null, 'sortorder', '*', 0, 1)) { + $category = reset($category); + + } else { + $cat = new object(); + $cat->name = get_string('miscellaneous'); + $cat->depth = 1; + $cat->sortorder = MAX_COURSES_IN_CATEGORY; + $cat->timemodified = time(); + if (!$catid = $DB->insert_record('course_categories', $cat)) { + print_error('cannotsetupcategory', 'error'); + } + // make sure category context exists + get_context_instance(CONTEXT_COURSECAT, $catid); + mark_context_dirty('/'.SYSCONTEXTID); + $category = $DB->get_record('course_categories', array('id'=>$catid)); } - $path = $path . '/' . $categoryid; - $depth = $depth + 1; + } + + return $category; +} + +/** + * Fixes course category and course sortorder, also verifies category and course parents and paths. + * (circular references are not fixed) + */ +function fix_course_sortorder() { + global $DB, $SITE; + + //WARNING: this is PHP5 only code! + + if ($unsorted = $DB->get_records('course_categories', array('sortorder'=>0))) { + //move all categories that are not sorted yet to the end + $DB->set_field('course_categories', 'sortorder', MAX_COURSES_IN_CATEGORY*MAX_COURSE_CATEGORIES, array('sortorder'=>0)); + } - if ($cat->path !== $path) { - $DB->set_field('course_categories', 'path', $path, array('id'=>$categoryid)); + $allcats = $DB->get_records('course_categories', null, 'sortorder, id', 'id, sortorder, parent, depth, path'); + $topcats = array(); + $brokencats = array(); + foreach ($allcats as $cat) { + $sortorder = (int)$cat->sortorder; + if (!$cat->parent) { + while(isset($topcats[$sortorder])) { + $sortorder++; + } + $topcats[$sortorder] = $cat; + continue; } - if ($cat->depth != $depth) { - $DB->set_field('course_categories', 'depth', $depth, array('id'=>$categoryid)); + if (!isset($allcats[$cat->parent])) { + $brokencats[] = $cat; + continue; } + if (!isset($allcats[$cat->parent]->children)) { + $allcats[$cat->parent]->children = array(); + } + while(isset($allcats[$cat->parent]->children[$sortorder])) { + $sortorder++; + } + $allcats[$cat->parent]->children[$sortorder] = $cat; } + unset($allcats); - // get some basic info about courses in the category - $info = $DB->get_record_sql("SELECT MIN(sortorder) AS min, - MAX(sortorder) AS max, - COUNT(sortorder) AS count - FROM {course} - WHERE category=?", array($categoryid)); - if ($info) { // no courses? - $max = $info->max; - $count = $info->count; - $min = $info->min; - unset($info); + // add broken cats to category tree + if ($brokencats) { + $defaultcat = reset($topcats); + foreach ($brokencats as $cat) { + $topcats[] = $cat; + } } - if ($categoryid > 0 && $n==0) { // only passed category so don't shift it - $n = $min; + // now walk recursively the tree and fix any problems found + $sortorder = 0; + $fixcontexts = array(); + _fix_course_cats($topcats, $sortorder, 0, 0, '', $fixcontexts); + + // detect if there are "multiple" frontpage courses and fix them if needed + $frontcourses = $DB->get_records('course', array('category'=>0), 'id'); + if (count($frontcourses) > 1) { + if (isset($frontcourses[SITEID])) { + $frontcourse = $frontcourses[SITEID]; + unset($frontcourses[SITEID]); + } else { + $frontcourse = array_shift($frontcourses); + } + $defaultcat = reset($topcats); + foreach ($frontcourses as $course) { + $DB->set_field('course', 'category', $defaultcat->id, array('id'=>$course->id)); + $context = get_context_instance(CONTEXT_COURSE, $course->id); + $fixcontexts[$context->id] = $context; + } + unset($frontcourses); + } else { + $frontcourse = reset($frontcourses); } - // $hasgap flag indicates whether there's a gap in the sequence - $hasgap = false; - if ($max-$min+1 != $count) { - $hasgap = true; + // now fix the paths and depths in context table if needed + if ($fixcontexts) { + rebuild_contexts($fixcontexts); } - // $mustshift indicates whether the sequence must be shifted to - // meet its range - $mustshift = false; - if ($min < $n+$tolerance || $min > $n+$tolerance+$catgap ) { - $mustshift = true; + // release memory + unset($topcats); + unset($brokencats); + unset($fixcontexts); + + // fix frontpage course sortorder + if ($frontcourse->sortorder != 1) { + $DB->set_field('course', 'sortorder', 1, array('id'=>$frontcourse->id)); } - // actually sort only if there are courses, - // and we meet one ofthe triggers: - // - safe flag - // - they are not in a continuos block - // - they are too close to the 'bottom' - if ($count && ( $safe || $hasgap || $mustshift ) ) { - // special, optimized case where all we need is to shift - if ( $mustshift && !$safe && !$hasgap) { - $shift = $n + $catgap - $min; - if ($shift < $count) { - $shift = $count + $catgap; - } - // UPDATE course SET sortorder=sortorder+$shift - $DB->execute("UPDATE {course} - SET sortorder=sortorder+? - WHERE category=?", array($shift, $categoryid)); - $n = $n + $catgap + $count; - - } else { // do it slowly - $n = $n + $catgap; - // if the new sequence overlaps the current sequence, lack of transactions - // will stop us -- shift things aside for a moment... - if ($safe || ($n >= $min && $n+$count+1 < $min && $CFG->dbfamily==='mysql')) { - $shift = $max + $n + 1000; - $DB->execute("UPDATE {course} - SET sortorder=sortorder+? - WHERE category=?", array($shift, $categoryid)); - } + // now fix the course counts in category records if needed + $sql = "SELECT cc.id, cc.coursecount, COUNT(c.id) AS newcount + FROM {course_categories} cc + LEFT JOIN {course} c ON c.category = cc.id + GROUP BY cc.id, cc.coursecount + HAVING cc.coursecount <> COUNT(c.id)"; - $courses = get_courses($categoryid, 'c.sortorder ASC', 'c.id,c.sortorder'); - $DB->begin_sql(); - $tx = true; // transaction sanity - foreach ($courses as $course) { - if ($tx && $course->sortorder != $n ) { // save db traffic - $tx = $tx && $DB->set_field('course', 'sortorder', $n, array('id'=>$course->id)); - } - $n++; - } - if ($tx) { - $DB->commit_sql(); - } else { - $DB->rollback_sql(); - if (!$safe) { - // if we failed when called with !safe, try - // to recover calling self with safe=true - return fix_course_sortorder($categoryid, $n, true, $depth, $path); - } - } - } + if ($updatecounts = $DB->get_records_sql($sql)) { + foreach ($updatecounts as $cat) { + $cat->coursecount = $cat->newcount; + unset($cat->newcount); + $DB->update_record_raw('course_categories', $cat, true); + } } - $DB->set_field('course_categories', 'coursecount', $count, array('id'=>$categoryid)); - // $n could need updating - $max = $DB->get_field_sql("SELECT MAX(sortorder) FROM {course} WHERE category=?", array($categoryid)); - if ($max > $n) { - $n = $max; + // now make sure that sortorders in course table are withing the category sortorder ranges + $sql = "SELECT cc.id, cc.sortorder + FROM {course_categories} cc + JOIN {course} c ON c.category = cc.id + WHERE c.sortorder < cc.sortorder OR c.sortorder > cc.sortorder + ".MAX_COURSES_IN_CATEGORY; + + if ($fixcategories = $DB->get_records_sql($sql)) { + //fix the course sortorder ranges + foreach ($fixcategories as $cat) { + $sql = "UPDATE {course} + SET sortorder = (sortorder % ".MAX_COURSES_IN_CATEGORY.") + ? + WHERE category = ?"; + $DB->execute($sql, array($cat->sortorder, $cat->id)); + } } + unset($fixcategories); + + // categories having courses with sortorder duplicates or having gaps in sortorder + $sql = "SELECT DISTINCT c1.category AS id , cc.sortorder + FROM {course} c1 + JOIN {course} c2 ON c1.sortorder = c2.sortorder + JOIN {course_categories} cc ON (c1.category = cc.id) + WHERE c1.id <> c2.id"; + $fixcategories = $DB->get_records_sql($sql); + + $sql = "SELECT cc.id, cc.sortorder, cc.coursecount, MAX(c.sortorder) AS maxsort, MIN(c.sortorder) AS minsort + FROM {course_categories} cc + JOIN {course} c ON c.category = cc.id + GROUP BY cc.id, cc.sortorder, cc.coursecount + HAVING (MAX(c.sortorder) <> cc.sortorder + cc.coursecount) OR (MIN(c.sortorder) <> cc.sortorder + 1)"; + $gapcategories = $DB->get_records_sql($sql); + + foreach ($gapcategories as $cat) { + if (isset($fixcategories[$cat->id])) { + // duplicates detected already + + } else if ($cat->minsort == $cat->sortorder and $cat->maxsort == $cat->sortorder + $cat->coursecount - 1) { + // easy - new course inserted with sortorder 0, the rest is ok + $sql = "UPDATE {course} + SET sortorder = sortorder + 1 + WHERE category = ?"; + $DB->execute($sql, array($cat->id)); - if ($categories = get_categories($categoryid)) { - foreach ($categories as $category) { - $n = fix_course_sortorder($category->id, $n, $safe, $depth, $path); + } else { + // it needs full resorting + $fixcategories[$cat->id] = $cat; } } + unset($gapcategories); - return $n+1; + // fix course sortorders in problematic categories only + foreach ($fixcategories as $cat) { + $i = 1; + $courses = $DB->get_records('course', array('category'=>$cat->id), 'sortorder ASC, id DESC', 'id, sortorder'); + foreach ($courses as $course) { + if ($course->sortorder != $cat->sortorder + $i) { + $course->sortorder = $cat->sortorder + $i; + $DB->update_record_raw('course', $course, true); + } + $i++; + } + } } /** - * Ensure all courses have a valid course category - * useful if a category has been removed manually - **/ -function fix_coursecategory_orphans() { + * Internal recursive category verification function, do not use directly! + */ +function _fix_course_cats($children, &$sortorder, $parent, $depth, $path, &$fixcontexts) { global $DB; - // Note: the handling of sortorder here is arguably - // open to race conditions. Hard to fix here, unlikely - // to hit anyone in production. - - $sql = "SELECT c.id, c.category, c.shortname - FROM {course} c - LEFT OUTER JOIN {course_categories} cc ON c.category=cc.id - WHERE cc.id IS NULL AND c.id <> " . SITEID; - - if (!$rs = $DB->get_recordset_sql($sql)) { - return; - } - - if ($rs->valid()) { // we have some orphans - - // the "default" category is the lowest numbered... - $default = $DB->get_field_sql("SELECT MIN(id) - FROM {course_categories}"); - $sortorder = $DB->get_field_sql("SELECT MAX(sortorder) - FROM {course} - WHERE category=?", array($default)); + $depth++; + foreach ($children as $cat) { + $sortorder = $sortorder + MAX_COURSES_IN_CATEGORY; + $update = false; + if ($parent != $cat->parent or $depth != $cat->depth or $path.'/'.$cat->id != $cat->path) { + $cat->parent = $parent; + $cat->depth = $depth; + $cat->path = $path.'/'.$cat->id; + $update = true; - $DB->begin_sql(); - foreach ($rs as $course) { - if (!$DB->set_field('course', 'category', $default, array('id'=>$course->id)) - or !$DB->set_field('course', 'sortorder', ++$sortorder, array('id'=>$course->id))) { - $DB->rollback_sql(); - return; - } + // make sure context caches are rebuild and dirty contexts marked + $context = get_context_instance(CONTEXT_COURSECAT, $cat->id); + $fixcontexts[$context->id] = $context; + } + if ($cat->sortorder != $sortorder) { + $cat->sortorder = $sortorder; + $update = true; + } + if ($update) { + $DB->update_record('course_categories', $cat, true); + } + if (isset($cat->children)) { + _fix_course_cats($cat->children, $sortorder, $cat->id, $cat->depth, $cat->path, $fixcontexts); } - $DB->commit_sql(); } - $rs->close(); } /** -- 2.39.5