protected function define_execution() { global $DB; // Although there is some sort of auto-recovery of missing sections // present in course/formats... here we check that all the sections // from 0 to MAX(section->section) exist, creating them if necessary $maxsection = $DB->get_field('course_sections', 'MAX(section)', array('course' => $this->get_courseid())); // Iterate over all sections for ($i = 0; $i <= $maxsection; $i++) { // If the section $i doesn't exist, create it if (!$DB->record_exists('course_sections', array('course' => $this->get_courseid(), 'section' => $i))) { $sectionrec = array('course' => $this->get_courseid(), 'section' => $i); $DB->insert_record('course_sections', $sectionrec); // missing section created } } // Rebuild cache now that all sections are in place rebuild_course_cache($this->get_courseid()); cache_helper::purge_by_event('changesincourse'); cache_helper::purge_by_event('changesincoursecat'); }
/** * Changes the sort order of courses in a category so that the first course appears after the second. * * @param int|stdClass $courseorid The course to focus on. * @param int $moveaftercourseid The course to shifter after or 0 if you want it to be the first course in the category. * @return bool */ function course_change_sortorder_after_course($courseorid, $moveaftercourseid) { global $DB; if (!is_object($courseorid)) { $course = get_course($courseorid); } else { $course = $courseorid; } if ((int) $moveaftercourseid === 0) { // We've moving the course to the start of the queue. $sql = 'SELECT sortorder FROM {course} WHERE category = :categoryid ORDER BY sortorder'; $params = array('categoryid' => $course->category); $sortorder = $DB->get_field_sql($sql, $params, IGNORE_MULTIPLE); $sql = 'UPDATE {course} SET sortorder = sortorder + 1 WHERE category = :categoryid AND id <> :id'; $params = array('categoryid' => $course->category, 'id' => $course->id); $DB->execute($sql, $params); $DB->set_field('course', 'sortorder', $sortorder, array('id' => $course->id)); } else { if ($course->id === $moveaftercourseid) { // They're the same - moronic. debugging("Invalid move after course given.", DEBUG_DEVELOPER); return false; } else { // Moving this course after the given course. It could be before it could be after. $moveaftercourse = get_course($moveaftercourseid); if ($course->category !== $moveaftercourse->category) { debugging("Cannot re-order courses. The given courses do not belong to the same category.", DEBUG_DEVELOPER); return false; } // Increment all courses in the same category that are ordered after the moveafter course. // This makes a space for the course we're moving. $sql = 'UPDATE {course} SET sortorder = sortorder + 1 WHERE category = :categoryid AND sortorder > :sortorder'; $params = array('categoryid' => $moveaftercourse->category, 'sortorder' => $moveaftercourse->sortorder); $DB->execute($sql, $params); $DB->set_field('course', 'sortorder', $moveaftercourse->sortorder + 1, array('id' => $course->id)); } } fix_course_sortorder(); cache_helper::purge_by_event('changesincourse'); return true; }
/** * Changes the sort order of this categories parent shifting this category up or down one. * * @global \moodle_database $DB * @param bool $up If set to true the category is shifted up one spot, else its moved down. * @return bool True on success, false otherwise. */ public function change_sortorder_by_one($up) { global $DB; $params = array($this->sortorder, $this->parent); if ($up) { $select = 'sortorder < ? AND parent = ?'; $sort = 'sortorder DESC'; } else { $select = 'sortorder > ? AND parent = ?'; $sort = 'sortorder ASC'; } fix_course_sortorder(); $swapcategory = $DB->get_records_select('course_categories', $select, $params, $sort, '*', 0, 1); $swapcategory = reset($swapcategory); if ($swapcategory) { $DB->set_field('course_categories', 'sortorder', $swapcategory->sortorder, array('id' => $this->id)); $DB->set_field('course_categories', 'sortorder', $this->sortorder, array('id' => $swapcategory->id)); $this->sortorder = $swapcategory->sortorder; $event = \core\event\course_category_updated::create(array('objectid' => $this->id, 'context' => $this->get_context())); $event->set_legacy_logdata(array(SITEID, 'category', 'move', 'management.php?categoryid=' . $this->id, $this->id)); $event->trigger(); // Finally reorder courses. fix_course_sortorder(); cache_helper::purge_by_event('changesincoursecat'); return true; } return false; }
if ($movecourse = $DB->get_record('course', array('id' => $moveup))) { $swapcourse = $DB->get_record('course', array('sortorder' => $movecourse->sortorder - 1)); } } else { if ($movecourse = $DB->get_record('course', array('id' => $movedown))) { $swapcourse = $DB->get_record('course', array('sortorder' => $movecourse->sortorder + 1)); } } if ($swapcourse and $movecourse) { // Check course's category. if ($movecourse->category != $id) { print_error('coursedoesnotbelongtocategory'); } $DB->set_field('course', 'sortorder', $swapcourse->sortorder, array('id' => $movecourse->id)); $DB->set_field('course', 'sortorder', $movecourse->sortorder, array('id' => $swapcourse->id)); cache_helper::purge_by_event('changesincourse'); add_to_log($movecourse->id, "course", "move", "edit.php?id=$movecourse->id", $movecourse->id); } } // Prepare the standard URL params for this page. We'll need them later. $urlparams = array('categoryid' => $id); if ($page) { $urlparams['page'] = $page; } if ($perpage) { $urlparams['perpage'] = $perpage; } $urlparams += $searchcriteria; $PAGE->set_pagelayout('coursecategory');
/** * Fixes course category and course sortorder, also verifies category and course parents and paths. * (circular references are not fixed) * * @global object * @global object * @uses MAX_COURSES_IN_CATEGORY * @uses MAX_COURSE_CATEGORIES * @uses SITEID * @uses CONTEXT_COURSE * @return void */ function fix_course_sortorder() { global $DB, $SITE; //WARNING: this is PHP5 only code! // if there are any changes made to courses or categories we will trigger // the cache events to purge all cached courses/categories data $cacheevents = array(); 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)); $cacheevents['changesincoursecat'] = true; } $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 (!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); // add broken cats to category tree if ($brokencats) { $defaultcat = reset($topcats); foreach ($brokencats as $cat) { $topcats[] = $cat; } } // now walk recursively the tree and fix any problems found $sortorder = 0; $fixcontexts = array(); if (_fix_course_cats($topcats, $sortorder, 0, 0, '', $fixcontexts)) { $cacheevents['changesincoursecat'] = true; } // 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 = context_course::instance($course->id); $fixcontexts[$context->id] = $context; $cacheevents['changesincourse'] = true; } unset($frontcourses); } else { $frontcourse = reset($frontcourses); } // now fix the paths and depths in context table if needed if ($fixcontexts) { foreach ($fixcontexts as $fixcontext) { $fixcontext->reset_paths(false); } context_helper::build_all_paths(false); unset($fixcontexts); $cacheevents['changesincourse'] = true; $cacheevents['changesincoursecat'] = 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)); $cacheevents['changesincourse'] = true; } // now fix the course counts in category records if needed $sql = "SELECT cc.id, cc.coursecount, COUNT(c.id) AS newcount\n FROM {course_categories} cc\n LEFT JOIN {course} c ON c.category = cc.id\n GROUP BY cc.id, cc.coursecount\n HAVING cc.coursecount <> COUNT(c.id)"; if ($updatecounts = $DB->get_records_sql($sql)) { // categories with more courses than MAX_COURSES_IN_CATEGORY $categories = array(); foreach ($updatecounts as $cat) { $cat->coursecount = $cat->newcount; if ($cat->coursecount >= MAX_COURSES_IN_CATEGORY) { $categories[] = $cat->id; } unset($cat->newcount); $DB->update_record_raw('course_categories', $cat, true); } if (!empty($categories)) { $str = implode(', ', $categories); debugging("The number of courses (category id: {$str}) has reached MAX_COURSES_IN_CATEGORY (" . MAX_COURSES_IN_CATEGORY . "), it will cause a sorting performance issue, please increase the value of MAX_COURSES_IN_CATEGORY in lib/datalib.php file. See tracker issue: MDL-25669", DEBUG_DEVELOPER); } $cacheevents['changesincoursecat'] = true; } // now make sure that sortorders in course table are withing the category sortorder ranges $sql = "SELECT DISTINCT cc.id, cc.sortorder\n FROM {course_categories} cc\n JOIN {course} c ON c.category = cc.id\n 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}\n SET sortorder = " . $DB->sql_modulo('sortorder', MAX_COURSES_IN_CATEGORY) . " + ?\n WHERE category = ?"; $DB->execute($sql, array($cat->sortorder, $cat->id)); } $cacheevents['changesincoursecat'] = true; } unset($fixcategories); // categories having courses with sortorder duplicates or having gaps in sortorder $sql = "SELECT DISTINCT c1.category AS id , cc.sortorder\n FROM {course} c1\n JOIN {course} c2 ON c1.sortorder = c2.sortorder\n JOIN {course_categories} cc ON (c1.category = cc.id)\n 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\n FROM {course_categories} cc\n JOIN {course} c ON c.category = cc.id\n GROUP BY cc.id, cc.sortorder, cc.coursecount\n 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}\n SET sortorder = sortorder + 1\n WHERE category = ?"; $DB->execute($sql, array($cat->id)); } else { // it needs full resorting $fixcategories[$cat->id] = $cat; } } $cacheevents['changesincourse'] = true; } unset($gapcategories); // 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); $cacheevents['changesincourse'] = true; } $i++; } } // advise all caches that need to be rebuilt foreach (array_keys($cacheevents) as $event) { cache_helper::purge_by_event($event); } }
/** * Test the countall function */ public function test_count_all() { global $DB; // Dont assume there is just one. An add-on might create a category as part of the install. $numcategories = $DB->count_records('course_categories'); $this->assertEquals($numcategories, coursecat::count_all()); $category1 = coursecat::create(array('name' => 'Cat1')); $category2 = coursecat::create(array('name' => 'Cat2', 'parent' => $category1->id)); $category3 = coursecat::create(array('name' => 'Cat3', 'parent' => $category2->id, 'visible' => 0)); // Now we've got three more. $this->assertEquals($numcategories + 3, coursecat::count_all()); cache_helper::purge_by_event('changesincoursecat'); // We should still have 4. $this->assertEquals($numcategories + 3, coursecat::count_all()); }
/** * Show course category and restores visibility for child course and subcategories * * Note that there is no capability check inside this function * * This function does not update field course_categories.timemodified * If you want to update timemodified, use * $coursecat->update(array('visible' => 1)); */ public function show() { if ($this->show_raw()) { cache_helper::purge_by_event('changesincoursecat'); add_to_log(SITEID, "category", "show", "editcategory.php?id={$this->id}", $this->id); } }
/** * Tests application cache event purge */ public function test_application_event_purge() { $instance = cache_config_phpunittest::instance(); $instance->phpunit_add_definition('phpunit/eventpurgetest', array('mode' => cache_store::MODE_APPLICATION, 'component' => 'phpunit', 'area' => 'eventpurgetest', 'invalidationevents' => array('crazyevent'))); $cache = cache::make('phpunit', 'eventpurgetest'); $this->assertTrue($cache->set('testkey1', 'test data 1')); $this->assertEquals('test data 1', $cache->get('testkey1')); $this->assertTrue($cache->set('testkey2', 'test data 2')); $this->assertEquals('test data 2', $cache->get('testkey2')); // Purge the event. cache_helper::purge_by_event('crazyevent'); // Check things have been removed. $this->assertFalse($cache->get('testkey1')); $this->assertFalse($cache->get('testkey2')); }
/** * Update a course. * * Please note this functions does not verify any access control, * the calling code is responsible for all validation (usually it is the form definition). * * @param object $data - all the data needed for an entry in the 'course' table * @param array $editoroptions course description editor options * @return void */ function update_course($data, $editoroptions = NULL) { global $CFG, $DB; $data->timemodified = time(); $oldcourse = course_get_format($data->id)->get_course(); $context = context_course::instance($oldcourse->id); if ($editoroptions) { $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0); } if ($overviewfilesoptions = course_overviewfiles_options($data->id)) { $data = file_postupdate_standard_filemanager($data, 'overviewfiles', $overviewfilesoptions, $context, 'course', 'overviewfiles', 0); } if (!isset($data->category) or empty($data->category)) { // prevent nulls and 0 in category field unset($data->category); } $changesincoursecat = $movecat = (isset($data->category) and $oldcourse->category != $data->category); if (!isset($data->visible)) { // data not from form, add missing visibility info $data->visible = $oldcourse->visible; } if ($data->visible != $oldcourse->visible) { // reset the visibleold flag when manually hiding/unhiding course $data->visibleold = $data->visible; $changesincoursecat = true; } else { if ($movecat) { $newcategory = $DB->get_record('course_categories', array('id' => $data->category)); if (empty($newcategory->visible)) { // make sure when moving into hidden category the course is hidden automatically $data->visible = 0; } } } // Update with the new data $DB->update_record('course', $data); // make sure the modinfo cache is reset rebuild_course_cache($data->id); // update course format options with full course data course_get_format($data->id)->update_course_format_options($data, $oldcourse); $course = $DB->get_record('course', array('id' => $data->id)); if ($movecat) { $newparent = context_coursecat::instance($course->category); context_moved($context, $newparent); } fix_course_sortorder(); // purge appropriate caches in case fix_course_sortorder() did not change anything cache_helper::purge_by_event('changesincourse'); if ($changesincoursecat) { cache_helper::purge_by_event('changesincoursecat'); } // Test for and remove blocks which aren't appropriate anymore blocks_remove_inappropriate($course); // Save any custom role names. save_local_role_names($course->id, $data); // update enrol settings enrol_course_updated(false, $course, $data); add_to_log($course->id, "course", "update", "edit.php?id={$course->id}", $course->id); // Trigger events events_trigger('course_updated', $course); if ($oldcourse->format !== $course->format) { // Remove all options stored for the previous format // We assume that new course format migrated everything it needed watching trigger // 'course_updated' and in method format_XXX::update_course_format_options() $DB->delete_records('course_format_options', array('courseid' => $course->id, 'format' => $oldcourse->format)); } }
/** * Resets all course/items session caches - useful in unittests when we change users and enrolments. */ public static function reset_caches() { cache_helper::purge_by_event('resettagindexbuilder'); }
public function test_get_children() { $category1 = coursecat::create(array('name' => 'Cat1')); $category2 = coursecat::create(array('name' => 'Cat2', 'parent' => $category1->id)); $category3 = coursecat::create(array('name' => 'Cat3', 'parent' => $category1->id, 'visible' => 0)); $category4 = coursecat::create(array('name' => 'Cat4', 'idnumber' => '12', 'parent' => $category1->id)); $category5 = coursecat::create(array('name' => 'Cat5', 'idnumber' => '11', 'parent' => $category1->id, 'visible' => 0)); $category6 = coursecat::create(array('name' => 'Cat6', 'idnumber' => '10', 'parent' => $category1->id)); $category7 = coursecat::create(array('name' => 'Cat0', 'parent' => $category1->id)); $children = $category1->get_children(); // user does not have the capability to view hidden categories, so the list should be // 2,4,6,7 $this->assertEquals(array($category2->id, $category4->id, $category6->id, $category7->id), array_keys($children)); $this->assertEquals(4, $category1->get_children_count()); $children = $category1->get_children(array('offset' => 2)); $this->assertEquals(array($category6->id, $category7->id), array_keys($children)); $this->assertEquals(4, $category1->get_children_count()); $children = $category1->get_children(array('limit' => 2)); $this->assertEquals(array($category2->id, $category4->id), array_keys($children)); $children = $category1->get_children(array('offset' => 1, 'limit' => 2)); $this->assertEquals(array($category4->id, $category6->id), array_keys($children)); $children = $category1->get_children(array('sort' => array('name' => 1))); // must be 7,2,4,6 $this->assertEquals(array($category7->id, $category2->id, $category4->id, $category6->id), array_keys($children)); $children = $category1->get_children(array('sort' => array('idnumber' => 1, 'name' => -1))); // must be 2,7,6,4 $this->assertEquals(array($category2->id, $category7->id, $category6->id, $category4->id), array_keys($children)); // check that everything is all right after purging the caches cache_helper::purge_by_event('changesincoursecat'); $children = $category1->get_children(); $this->assertEquals(array($category2->id, $category4->id, $category6->id, $category7->id), array_keys($children)); $this->assertEquals(4, $category1->get_children_count()); }
/** * Cleans the cache. * * Aggressive deletion to be conservative given the gradebook design. * The key is based on the requested params, not easy nor worth to purge selectively. * * @return void */ public static function clean_record_set() { cache_helper::purge_by_event('changesingradecategories'); }
/** * Changes the sort order of this categories parent shifting this category up or down one. * * @global \moodle_database $DB * @param bool $up If set to true the category is shifted up one spot, else its moved down. * @return bool True on success, false otherwise. */ public function change_sortorder_by_one($up) { global $DB; $params = array($this->sortorder, $this->parent); if ($up) { $select = 'sortorder < ? AND parent = ?'; $sort = 'sortorder DESC'; } else { $select = 'sortorder > ? AND parent = ?'; $sort = 'sortorder ASC'; } fix_course_sortorder(); $swapcategory = $DB->get_records_select('course_categories', $select, $params, $sort, '*', 0, 1); $swapcategory = reset($swapcategory); if ($swapcategory) { $DB->set_field('course_categories', 'sortorder', $swapcategory->sortorder, array('id' => $this->id)); $DB->set_field('course_categories', 'sortorder', $this->sortorder, array('id' => $swapcategory->id)); $this->sortorder = $swapcategory->sortorder; add_to_log(SITEID, "category", "move", "management.php?categoryid={$this->id}", $this->id); // Finally reorder courses. fix_course_sortorder(); cache_helper::purge_by_event('changesincoursecat'); return true; } return false; }
/** * Test the countall function */ public function test_count_all() { // There should be just the default category. $this->assertEquals(1, coursecat::count_all()); $category1 = coursecat::create(array('name' => 'Cat1')); $category2 = coursecat::create(array('name' => 'Cat2', 'parent' => $category1->id)); $category3 = coursecat::create(array('name' => 'Cat3', 'parent' => $category2->id, 'visible' => 0)); // Now we've got four. $this->assertEquals(4, coursecat::count_all()); cache_helper::purge_by_event('changesincoursecat'); // We should still have 4. $this->assertEquals(4, coursecat::count_all()); }