diff --git a/availability/condition/completion/tests/behat/availability_completion.feature b/availability/condition/completion/tests/behat/availability_completion.feature index 67743d3f1c0..0f4c121d968 100644 --- a/availability/condition/completion/tests/behat/availability_completion.feature +++ b/availability/condition/completion/tests/behat/availability_completion.feature @@ -54,64 +54,3 @@ Feature: availability_completion # Mark page 1 complete When I toggle the manual completion state of "Page 1" Then I should see "Page 2" in the "region-main" "region" - - @javascript - Scenario: test completion and course cache rebuild - Given the following "activities" exist: - | activity | name | intro | course | idnumber | - | forum | forum 1 | forum 1 | C1 | forum1 | - And I log in as "teacher1" - And I am on "Course 1" course homepage with editing mode on - And I open "forum 1" actions menu - And I click on "Edit settings" "link" in the "forum 1" activity - And I set the following fields to these values: - | Completion tracking | Show activity as complete when conditions are met | - | completionview | 1 | - | completionpostsenabled | 1 | - | completionposts | 2 | - And I press "Save and return to course" - - And I add a new discussion to "forum 1" forum with: - | Subject | Forum post 1 | - | Message | This is the body | - - And I am on "Course 1" course homepage with editing mode on - And I add a "Page" to section "2" - And I set the following fields to these values: - | Name | Page 2 | - | Description | Test | - | Page content | Test | - And I expand all fieldsets - And I press "Add restriction..." - And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" - And I click on ".availability-item .availability-eye img" "css_element" - And I set the following fields to these values: - | Required completion status | must be marked complete | - | cm | forum 1 | - And I press "Save and return to course" - - When I log out - And I log in as "student1" - And I am on "Course 1" course homepage - - # Page 2 should not appear yet. - Then I should not see "Page 2" in the "region-main" "region" - And I click on "forum 1" "link" in the "region-main" "region" - # Page 2 should not appear yet. - Then I should not see "Page 2" in the "region-main" "region" - - When I log out - And I log in as "teacher1" - And I am on "Course 1" course homepage - And I am on the "forum 1" "forum activity editing" page - And I expand all fieldsets - And I set the following fields to these values: - | completionpostsenabled | 0 | - And I press "Save and display" - - When I log out - And I log in as "student1" - And I am on "Course 1" course homepage - And I click on "forum 1" "link" in the "region-main" "region" - And I am on "Course 1" course homepage - Then I should see "Page 2" in the "region-main" "region" diff --git a/availability/condition/date/classes/condition.php b/availability/condition/date/classes/condition.php index fdf2f705006..63b61b4ebce 100644 --- a/availability/condition/date/classes/condition.php +++ b/availability/condition/date/classes/condition.php @@ -292,15 +292,14 @@ class condition extends \core_availability\condition { $updatesection->availability = json_encode($tree->save()); $updatesection->timemodified = time(); $DB->update_record('course_sections', $updatesection); - course_purge_section_cache($section); $anychanged = true; } } + // Ensure course cache is cleared if required. if ($anychanged) { - // Partial rebuild the sections which have been invalidated. - rebuild_course_cache($courseid, true, true); + rebuild_course_cache($courseid, true); } } } diff --git a/course/dnduploadlib.php b/course/dnduploadlib.php index 00216dc4441..cf23b5da001 100644 --- a/course/dnduploadlib.php +++ b/course/dnduploadlib.php @@ -614,10 +614,8 @@ class dndupload_ajax_processor { $visible = get_fast_modinfo($this->course)->get_section_info($this->section)->visible; $DB->set_field('course_modules', 'instance', $instanceid, array('id' => $this->cm->id)); - - course_purge_module_cache($this->cm); // Rebuild the course cache after update action - rebuild_course_cache($this->course->id, true, true); + rebuild_course_cache($this->course->id, true); $sectionid = course_add_cm_to_section($this->course, $this->cm->id, $this->section); diff --git a/course/format/classes/base.php b/course/format/classes/base.php index 98b95c7c9b9..079cc4acf2c 100644 --- a/course/format/classes/base.php +++ b/course/format/classes/base.php @@ -1009,15 +1009,7 @@ abstract class base { } } if ($needrebuild) { - if ($sectionid) { - $sectionrecord = $DB->get_record('course_sections', ['id' => $sectionid], 'course, section', MUST_EXIST); - course_purge_section_cache($sectionrecord); - // Partial rebuild sections that have been invalidated. - rebuild_course_cache($this->courseid, true, true); - } else { - // Full rebuild if sectionid is null. - rebuild_course_cache($this->courseid); - } + rebuild_course_cache($this->courseid, true); } if ($changed) { // Reset internal caches. @@ -1285,7 +1277,7 @@ abstract class base { } if (!is_object($section)) { $section = $DB->get_record('course_sections', array('course' => $this->get_courseid(), 'section' => $section), - 'id,course,section,sequence,summary'); + 'id,section,sequence,summary'); } if (!$section || !$section->section) { // Not possible to delete 0-section. @@ -1322,9 +1314,7 @@ abstract class base { // Delete section and it's format options. $DB->delete_records('course_format_options', array('sectionid' => $section->id)); $DB->delete_records('course_sections', array('id' => $section->id)); - course_purge_section_cache($section); - // Partial rebuild section cache that has been purged. - rebuild_course_cache($course->id, true, true); + rebuild_course_cache($course->id, true); // Delete section summary files. $context = \context_course::instance($course->id); diff --git a/course/format/singleactivity/tests/behat/activity_navigation.feature b/course/format/singleactivity/tests/behat/activity_navigation.feature index bbf71e78d96..7d3aed1dcff 100644 --- a/course/format/singleactivity/tests/behat/activity_navigation.feature +++ b/course/format/singleactivity/tests/behat/activity_navigation.feature @@ -25,15 +25,15 @@ Feature: Activity navigation in a single activity course Scenario: Step through hidden activities in the course as a teacher. Given I log in as "teacher1" When I am on "Course 1" course homepage - # The last activity (Forum 1) won't have the next activity link. - And "#next-activity-link" "css_element" should not exist - And I should see "Chat 1 (hidden)" in the "#prev-activity-link" "css_element" - And I follow "Chat 1 (hidden)" - And I should see "Forum 1" in the "#next-activity-link" "css_element" - And I should see "Assignment 1 (hidden)" in the "#prev-activity-link" "css_element" - And I follow "Assignment 1 (hidden)" + # The first activity (Forum 1) won't have the previous activity link. Then "#prev-activity-link" "css_element" should not exist + And I should see "Assignment 1 (hidden)" in the "#next-activity-link" "css_element" + And I follow "Assignment 1 (hidden)" + And I should see "Forum 1" in the "#prev-activity-link" "css_element" And I should see "Chat 1 (hidden)" in the "#next-activity-link" "css_element" + And I follow "Chat 1 (hidden)" + And I should see "Assignment 1 (hidden)" in the "#prev-activity-link" "css_element" + And "#next-activity-link" "css_element" should not exist Scenario: Jump to a hidden activity as a teacher Given I log in as "teacher1" @@ -45,17 +45,17 @@ Feature: Activity navigation in a single activity course And the "Jump to..." select box should contain "Assignment 1 (hidden)" And the "Jump to..." select box should contain "Chat 1 (hidden)" # Jump to a hidden activity somewhere in the middle. - When I select "Chat 1 (hidden)" from the "Jump to..." singleselect - Then I should see "Chat 1" - And I should see "Assignment 1 (hidden)" in the "#prev-activity-link" "css_element" - And I should see "Forum 1" in the "#next-activity-link" "css_element" - # Jump to the first activity. - And I select "Assignment 1 (hidden)" from the "Jump to..." singleselect + When I select "Assignment 1 (hidden)" from the "Jump to..." singleselect + Then I should see "Assignment 1" + And I should see "Forum 1" in the "#prev-activity-link" "css_element" And I should see "Chat 1 (hidden)" in the "#next-activity-link" "css_element" + # Jump to the first activity. + And I select "Forum 1" from the "Jump to..." singleselect + And I should see "Assignment 1 (hidden)" in the "#next-activity-link" "css_element" But "#prev-activity-link" "css_element" should not exist # Jump to the last activity. - And I select "Forum 1" from the "Jump to..." singleselect - And I should see "Chat 1 (hidden)" in the "#prev-activity-link" "css_element" + And I select "Chat 1 (hidden)" from the "Jump to..." singleselect + And I should see "Assignment 1 (hidden)" in the "#prev-activity-link" "css_element" But "#next-activity-link" "css_element" should not exist Scenario: The activity navigation controls are not available as a student. diff --git a/course/lib.php b/course/lib.php index 03d454fe95d..43733242a18 100644 --- a/course/lib.php +++ b/course/lib.php @@ -389,12 +389,8 @@ function course_integrity_check($courseid, $rawmods = null, $sections = null, $f /** * For a given course, returns an array of course activity objects * Each item in the array contains he following properties: - * - * @param int $courseid course id - * @param bool $usecache get activities from cache if modinfo exists when $usecache is true - * @return array list of activities */ -function get_array_of_activities(int $courseid, bool $usecache = false): array { +function get_array_of_activities($courseid) { // cm - course module id // mod - name of the module (eg forum) // section - the number of the section (eg week or topic) @@ -410,21 +406,12 @@ function get_array_of_activities(int $courseid, bool $usecache = false): array { throw new moodle_exception('courseidnotfound'); } + $mod = array(); + $rawmods = get_course_mods($courseid); if (empty($rawmods)) { - return []; + return $mod; // always return array } - - $mods = []; - if ($usecache) { - // Get existing cache. - $cachecoursemodinfo = cache::make('core', 'coursemodinfo'); - $coursemodinfo = $cachecoursemodinfo->get($courseid); - if ($coursemodinfo !== false) { - $mods = $coursemodinfo->modinfo; - } - } - $courseformat = course_get_format($course); if ($sections = $DB->get_records('course_sections', array('course' => $courseid), @@ -436,152 +423,140 @@ function get_array_of_activities(int $courseid, bool $usecache = false): array { $sections = $DB->get_records('course_sections', array('course' => $courseid), 'section ASC', 'id,section,sequence,visible'); } - // Build array of activities. - foreach ($sections as $section) { - if (!empty($section->sequence)) { - $cmids = explode(",", $section->sequence); - $numberofmods = count($cmids); - for ($order = 0; $order < $numberofmods; $order++) { - $cmid = $cmids[$order]; - // Activity not exist in database. - $notexistindb = empty($rawmods[$cmid]); - $activitycached = isset($mods[$cmid]); - if ($activitycached || $notexistindb) { - continue; - } - $modposition = ($order === 0) ? 0 : array_search($cmids[$order - 1], array_keys($mods)) + 1; - $mods = array_slice($mods, 0, $modposition, true) - + [$cmid => new stdClass()] - + array_slice($mods, $modposition, null, true); - - // Adjust visibleoncoursepage, value in DB may not respect format availability. - $rawmods[$cmid]->visibleoncoursepage = (!$rawmods[$cmid]->visible - || $rawmods[$cmid]->visibleoncoursepage + foreach ($sections as $section) { + if (!empty($section->sequence)) { + $sequence = explode(",", $section->sequence); + foreach ($sequence as $seq) { + if (empty($rawmods[$seq])) { + continue; + } + // Adjust visibleoncoursepage, value in DB may not respect format availability. + $rawmods[$seq]->visibleoncoursepage = (!$rawmods[$seq]->visible + || $rawmods[$seq]->visibleoncoursepage || empty($CFG->allowstealth) - || !$courseformat->allow_stealth_module_visibility($rawmods[$cmid], $section)) ? 1 : 0; + || !$courseformat->allow_stealth_module_visibility($rawmods[$seq], $section)) ? 1 : 0; - $mods[$cmid]->id = $rawmods[$cmid]->instance; - $mods[$cmid]->cm = $rawmods[$cmid]->id; - $mods[$cmid]->mod = $rawmods[$cmid]->modname; + // Create an object that will be cached. + $mod[$seq] = new stdClass(); + $mod[$seq]->id = $rawmods[$seq]->instance; + $mod[$seq]->cm = $rawmods[$seq]->id; + $mod[$seq]->mod = $rawmods[$seq]->modname; // Oh dear. Inconsistent names left here for backward compatibility. - $mods[$cmid]->section = $section->section; - $mods[$cmid]->sectionid = $rawmods[$cmid]->section; + $mod[$seq]->section = $section->section; + $mod[$seq]->sectionid = $rawmods[$seq]->section; - $mods[$cmid]->module = $rawmods[$cmid]->module; - $mods[$cmid]->added = $rawmods[$cmid]->added; - $mods[$cmid]->score = $rawmods[$cmid]->score; - $mods[$cmid]->idnumber = $rawmods[$cmid]->idnumber; - $mods[$cmid]->visible = $rawmods[$cmid]->visible; - $mods[$cmid]->visibleoncoursepage = $rawmods[$cmid]->visibleoncoursepage; - $mods[$cmid]->visibleold = $rawmods[$cmid]->visibleold; - $mods[$cmid]->groupmode = $rawmods[$cmid]->groupmode; - $mods[$cmid]->groupingid = $rawmods[$cmid]->groupingid; - $mods[$cmid]->indent = $rawmods[$cmid]->indent; - $mods[$cmid]->completion = $rawmods[$cmid]->completion; - $mods[$cmid]->extra = ""; - $mods[$cmid]->completiongradeitemnumber = - $rawmods[$cmid]->completiongradeitemnumber; - $mods[$cmid]->completionpassgrade = $rawmods[$cmid]->completionpassgrade; - $mods[$cmid]->completionview = $rawmods[$cmid]->completionview; - $mods[$cmid]->completionexpected = $rawmods[$cmid]->completionexpected; - $mods[$cmid]->showdescription = $rawmods[$cmid]->showdescription; - $mods[$cmid]->availability = $rawmods[$cmid]->availability; - $mods[$cmid]->deletioninprogress = $rawmods[$cmid]->deletioninprogress; + $mod[$seq]->module = $rawmods[$seq]->module; + $mod[$seq]->added = $rawmods[$seq]->added; + $mod[$seq]->score = $rawmods[$seq]->score; + $mod[$seq]->idnumber = $rawmods[$seq]->idnumber; + $mod[$seq]->visible = $rawmods[$seq]->visible; + $mod[$seq]->visibleoncoursepage = $rawmods[$seq]->visibleoncoursepage; + $mod[$seq]->visibleold = $rawmods[$seq]->visibleold; + $mod[$seq]->groupmode = $rawmods[$seq]->groupmode; + $mod[$seq]->groupingid = $rawmods[$seq]->groupingid; + $mod[$seq]->indent = $rawmods[$seq]->indent; + $mod[$seq]->completion = $rawmods[$seq]->completion; + $mod[$seq]->extra = ""; + $mod[$seq]->completiongradeitemnumber = + $rawmods[$seq]->completiongradeitemnumber; + $mod[$seq]->completionpassgrade = $rawmods[$seq]->completionpassgrade; + $mod[$seq]->completionview = $rawmods[$seq]->completionview; + $mod[$seq]->completionexpected = $rawmods[$seq]->completionexpected; + $mod[$seq]->showdescription = $rawmods[$seq]->showdescription; + $mod[$seq]->availability = $rawmods[$seq]->availability; + $mod[$seq]->deletioninprogress = $rawmods[$seq]->deletioninprogress; - $modname = $mods[$cmid]->mod; - $functionname = $modname . "_get_coursemodule_info"; + $modname = $mod[$seq]->mod; + $functionname = $modname."_get_coursemodule_info"; - if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) { - continue; - } + if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) { + continue; + } - include_once("$CFG->dirroot/mod/$modname/lib.php"); + include_once("$CFG->dirroot/mod/$modname/lib.php"); - if ($hasfunction = function_exists($functionname)) { - if ($info = $functionname($rawmods[$cmid])) { - if (!empty($info->icon)) { - $mods[$cmid]->icon = $info->icon; - } - if (!empty($info->iconcomponent)) { - $mods[$cmid]->iconcomponent = $info->iconcomponent; - } - if (!empty($info->name)) { - $mods[$cmid]->name = $info->name; - } - if ($info instanceof cached_cm_info) { - // When using cached_cm_info you can include three new fields. - // That aren't available for legacy code. - if (!empty($info->content)) { - $mods[$cmid]->content = $info->content; - } - if (!empty($info->extraclasses)) { - $mods[$cmid]->extraclasses = $info->extraclasses; - } - if (!empty($info->iconurl)) { - // Convert URL to string as it's easier to store. - // Also serialized object contains \0 byte, - // ... and can not be written to Postgres DB. - $url = new moodle_url($info->iconurl); - $mods[$cmid]->iconurl = $url->out(false); - } - if (!empty($info->onclick)) { - $mods[$cmid]->onclick = $info->onclick; - } - if (!empty($info->customdata)) { - $mods[$cmid]->customdata = $info->customdata; - } - } else { - // When using a stdclass, the (horrible) deprecated ->extra field, - // ... that is available for BC. - if (!empty($info->extra)) { - $mods[$cmid]->extra = $info->extra; - } - } - } - } - // When there is no modname_get_coursemodule_info function, - // ... but showdescriptions is enabled, then we use the 'intro', - // ... and 'introformat' fields in the module table. - if (!$hasfunction && $rawmods[$cmid]->showdescription) { - if ($modvalues = $DB->get_record($rawmods[$cmid]->modname, - ['id' => $rawmods[$cmid]->instance], 'name, intro, introformat')) { - // Set content from intro and introformat. Filters are disabled. - // Because we filter it with format_text at display time. - $mods[$cmid]->content = format_module_intro($rawmods[$cmid]->modname, - $modvalues, $rawmods[$cmid]->id, false); + if ($hasfunction = function_exists($functionname)) { + if ($info = $functionname($rawmods[$seq])) { + if (!empty($info->icon)) { + $mod[$seq]->icon = $info->icon; + } + if (!empty($info->iconcomponent)) { + $mod[$seq]->iconcomponent = $info->iconcomponent; + } + if (!empty($info->name)) { + $mod[$seq]->name = $info->name; + } + if ($info instanceof cached_cm_info) { + // When using cached_cm_info you can include three new fields + // that aren't available for legacy code + if (!empty($info->content)) { + $mod[$seq]->content = $info->content; + } + if (!empty($info->extraclasses)) { + $mod[$seq]->extraclasses = $info->extraclasses; + } + if (!empty($info->iconurl)) { + // Convert URL to string as it's easier to store. Also serialized object contains \0 byte and can not be written to Postgres DB. + $url = new moodle_url($info->iconurl); + $mod[$seq]->iconurl = $url->out(false); + } + if (!empty($info->onclick)) { + $mod[$seq]->onclick = $info->onclick; + } + if (!empty($info->customdata)) { + $mod[$seq]->customdata = $info->customdata; + } + } else { + // When using a stdclass, the (horrible) deprecated ->extra field + // is available for BC + if (!empty($info->extra)) { + $mod[$seq]->extra = $info->extra; + } + } + } + } + // When there is no modname_get_coursemodule_info function, + // but showdescriptions is enabled, then we use the 'intro' + // and 'introformat' fields in the module table + if (!$hasfunction && $rawmods[$seq]->showdescription) { + if ($modvalues = $DB->get_record($rawmods[$seq]->modname, + array('id' => $rawmods[$seq]->instance), 'name, intro, introformat')) { + // Set content from intro and introformat. Filters are disabled + // because we filter it with format_text at display time + $mod[$seq]->content = format_module_intro($rawmods[$seq]->modname, + $modvalues, $rawmods[$seq]->id, false); - // To save making another query just below, put name in here. - $mods[$cmid]->name = $modvalues->name; - } - } - if (!isset($mods[$cmid]->name)) { - $mods[$cmid]->name = $DB->get_field($rawmods[$cmid]->modname, "name", - ["id" => $rawmods[$cmid]->instance]); - } + // To save making another query just below, put name in here + $mod[$seq]->name = $modvalues->name; + } + } + if (!isset($mod[$seq]->name)) { + $mod[$seq]->name = $DB->get_field($rawmods[$seq]->modname, "name", array("id"=>$rawmods[$seq]->instance)); + } - // Minimise the database size by unsetting default options when they are 'empty'. - // This list corresponds to code in the cm_info constructor. - foreach (['idnumber', 'groupmode', 'groupingid', + // Minimise the database size by unsetting default options when they are + // 'empty'. This list corresponds to code in the cm_info constructor. + foreach (array('idnumber', 'groupmode', 'groupingid', 'indent', 'completion', 'extra', 'extraclasses', 'iconurl', 'onclick', 'content', 'icon', 'iconcomponent', 'customdata', 'availability', 'completionview', - 'completionexpected', 'score', 'showdescription', 'deletioninprogress'] as $property) { - if (property_exists($mods[$cmid], $property) && - empty($mods[$cmid]->{$property})) { - unset($mods[$cmid]->{$property}); - } - } - // Special case: this value is usually set to null, but may be 0. - if (property_exists($mods[$cmid], 'completiongradeitemnumber') && - is_null($mods[$cmid]->completiongradeitemnumber)) { - unset($mods[$cmid]->completiongradeitemnumber); - } - } + 'completionexpected', 'score', 'showdescription', 'deletioninprogress') as $property) { + if (property_exists($mod[$seq], $property) && + empty($mod[$seq]->{$property})) { + unset($mod[$seq]->{$property}); + } + } + // Special case: this value is usually set to null, but may be 0 + if (property_exists($mod[$seq], 'completiongradeitemnumber') && + is_null($mod[$seq]->completiongradeitemnumber)) { + unset($mod[$seq]->completiongradeitemnumber); + } + } } } } - return $mods; + return $mod; } /** @@ -839,7 +814,11 @@ function course_add_cm_to_section($courseorid, $cmid, $sectionnum, $beforemod = } $DB->set_field("course_sections", "sequence", $newsequence, array("id" => $section->id)); $DB->set_field('course_modules', 'section', $section->id, array('id' => $cmid)); - rebuild_course_cache($courseid, true); + if (is_object($courseorid)) { + rebuild_course_cache($courseorid->id, true); + } else { + rebuild_course_cache($courseorid, true); + } return $section->id; // Return course_sections ID that was used. } @@ -858,8 +837,7 @@ function set_coursemodule_groupmode($id, $groupmode) { $cm = $DB->get_record('course_modules', array('id' => $id), 'id,course,groupmode', MUST_EXIST); if ($cm->groupmode != $groupmode) { $DB->set_field('course_modules', 'groupmode', $groupmode, array('id' => $cm->id)); - course_purge_module_cache($cm); - rebuild_course_cache($cm->course, false, true); + rebuild_course_cache($cm->course, true); } return ($cm->groupmode != $groupmode); } @@ -869,8 +847,7 @@ function set_coursemodule_idnumber($id, $idnumber) { $cm = $DB->get_record('course_modules', array('id' => $id), 'id,course,idnumber', MUST_EXIST); if ($cm->idnumber != $idnumber) { $DB->set_field('course_modules', 'idnumber', $idnumber, array('id' => $cm->id)); - course_purge_module_cache($cm); - rebuild_course_cache($cm->course, false, true); + rebuild_course_cache($cm->course, true); } return ($cm->idnumber != $idnumber); } @@ -947,8 +924,7 @@ function set_coursemodule_visible($id, $visible, $visibleoncoursepage = 1) { } } - course_purge_module_cache($cm); - rebuild_course_cache($cm->course, false, true); + rebuild_course_cache($cm->course, true); return true; } @@ -985,8 +961,7 @@ function set_coursemodule_name($id, $name) { $DB->update_record($cm->modname, $module); $cm->name = $module->name; \core\event\course_module_updated::create_from_cm($cm)->trigger(); - course_purge_module_cache($cm); - rebuild_course_cache($cm->course, false, true); + rebuild_course_cache($cm->course, true); // Attempt to update the grade item if relevant. $grademodule = $DB->get_record($cm->modname, array('id' => $cm->instance)); @@ -1138,14 +1113,13 @@ function course_delete_module($cmid, $async = false) { 'context' => $modcontext, 'objectid' => $cm->id, 'other' => array( - 'modulename' => $modulename, + 'modulename' => $modulename, 'instanceid' => $cm->instance, ) )); $event->add_record_snapshot('course_modules', $cm); $event->trigger(); - course_purge_module_cache($cm); - rebuild_course_cache($cm->course, false, true); + rebuild_course_cache($cm->course, true); } /** @@ -1402,23 +1376,17 @@ function move_section_to($course, $section, $destination, $ignorenumsections = f $movedsections = reorder_sections($sections, $section, $destination); - $sectioninfo = new stdClass; - $sectioninfo->course = $course->id; // Update all sections. Do this in 2 steps to avoid breaking database // uniqueness constraint $transaction = $DB->start_delegated_transaction(); foreach ($movedsections as $id => $position) { if ($sections[$id] !== $position) { - $DB->set_field('course_sections', 'section', -$position, ['id' => $id]); - $sectioninfo->section = $id; - course_purge_section_cache($sectioninfo); + $DB->set_field('course_sections', 'section', -$position, array('id' => $id)); } } foreach ($movedsections as $id => $position) { if ($sections[$id] !== $position) { - $DB->set_field('course_sections', 'section', $position, ['id' => $id]); - $sectioninfo->section = $id; - course_purge_section_cache($sectioninfo); + $DB->set_field('course_sections', 'section', $position, array('id' => $id)); } } @@ -1426,14 +1394,14 @@ function move_section_to($course, $section, $destination, $ignorenumsections = f // Adjust the higlighted section location if we move something over it either direction. if ($section == $course->marker) { course_set_marker($course->id, $destination); - } else if ($section > $course->marker && $course->marker >= $destination) { + } elseif ($section > $course->marker && $course->marker >= $destination) { course_set_marker($course->id, $course->marker+1); - } else if ($section < $course->marker && $course->marker <= $destination) { + } elseif ($section < $course->marker && $course->marker <= $destination) { course_set_marker($course->id, $course->marker-1); } $transaction->allow_commit(); - rebuild_course_cache($course->id, true, true); + rebuild_course_cache($course->id, true); return true; } @@ -1608,8 +1576,7 @@ function course_update_section($course, $section, $data) { $data['id'] = $section->id; $data['timemodified'] = time(); $DB->update_record('course_sections', $data); - course_purge_section_cache($section); - rebuild_course_cache($courseid, false, true); + rebuild_course_cache($courseid, true); course_get_format($courseid)->update_section_format_options($data); // Update fields of the $section object. @@ -1641,13 +1608,11 @@ function course_update_section($course, $section, $data) { } else { // We hide the section, so we hide the module but we store the original state in visibleold. set_coursemodule_visible($moduleid, 0, $cm->visibleoncoursepage); - $DB->set_field('course_modules', 'visibleold', $cm->visible, ['id' => $moduleid]); - course_purge_module_cache($cm); + $DB->set_field('course_modules', 'visibleold', $cm->visible, array('id' => $moduleid)); } \core\event\course_module_updated::create_from_cm($cm)->trigger(); } } - rebuild_course_cache($courseid, false, true); } } @@ -5165,50 +5130,3 @@ function course_output_fragment_new_base_form($args) { return $o; } - -/** - * Purge the cache of a course section. - * - * $sectioninfo must have following attributes: - * - course: course id - * - section: section number - * - * @param object $sectioninfo section info - * @return void - */ -function course_purge_section_cache(object $sectioninfo): void { - $section = $sectioninfo->section; - $courseid = $sectioninfo->course; - $cache = cache::make('core', 'coursemodinfo'); - $cache->acquire_lock($courseid); - $coursemodinfo = $cache->get($courseid); - if (($coursemodinfo !== false) && array_key_exists($section, $coursemodinfo->sectioncache)) { - unset($coursemodinfo->sectioncache[$section]); - $cache->set($courseid, $coursemodinfo); - } - $cache->release_lock($courseid); -} - -/** - * Purge the cache of a course module. - * - * $cm must have following attributes: - * - id: cmid - * - course: course id - * - * @param cm_info|stdClass $cm course module - * @return void - */ -function course_purge_module_cache($cm): void { - $cmid = $cm->id; - $courseid = $cm->course; - $cache = cache::make('core', 'coursemodinfo'); - $cache->acquire_lock($courseid); - $coursemodinfo = $cache->get($courseid); - $hascache = ($coursemodinfo !== false) && array_key_exists($cmid, $coursemodinfo->modinfo); - if ($hascache) { - unset($coursemodinfo->modinfo[$cmid]); - $cache->set($courseid, $coursemodinfo); - } - $cache->release_lock($courseid); -} diff --git a/course/mod.php b/course/mod.php index 86fc2bfcda5..67719a7aae2 100644 --- a/course/mod.php +++ b/course/mod.php @@ -190,9 +190,7 @@ if ((!empty($movetosection) or !empty($moveto)) and confirm_sesskey()) { $DB->set_field('course_modules', 'indent', $cm->indent, array('id'=>$cm->id)); - course_purge_module_cache($cm); - // Rebuild invalidated module cache. - rebuild_course_cache($cm->course, false, true); + rebuild_course_cache($cm->course); redirect(course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn))); diff --git a/course/modlib.php b/course/modlib.php index 80e24b7b614..2afb5963cea 100644 --- a/course/modlib.php +++ b/course/modlib.php @@ -374,11 +374,7 @@ function edit_module_post_actions($moduleinfo, $course) { $moduleinfo->showgradingmanagement = $showgradingmanagement; } - $cminfo = new stdClass(); - $cminfo->course = $moduleinfo->course; - $cminfo->id = $moduleinfo->coursemodule; - course_purge_module_cache($cminfo); - rebuild_course_cache($course->id, true, true); + rebuild_course_cache($course->id, true); if ($hasgrades) { grade_regrade_final_grades($course->id); } diff --git a/course/tests/courselib_test.php b/course/tests/courselib_test.php index 9197e1a8ac6..32f66639e07 100644 --- a/course/tests/courselib_test.php +++ b/course/tests/courselib_test.php @@ -1099,7 +1099,6 @@ class core_course_courselib_testcase extends advanced_testcase { // Delete section in the middle (2). $this->assertFalse(course_delete_section($course, 2, false)); - $this->assertEquals(4, course_get_format($course)->get_last_section_number()); $this->assertTrue(course_delete_section($course, 2, true)); $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign21->cmid))); $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign22->cmid))); diff --git a/course/tests/externallib_test.php b/course/tests/externallib_test.php index 03621e5cfe2..48650aa6cc7 100644 --- a/course/tests/externallib_test.php +++ b/course/tests/externallib_test.php @@ -1085,9 +1085,8 @@ class externallib_test extends externallib_advanced_testcase { array('course' => $course->id, 'intro' => 'forum completion tracking auto', 'trackingtype' => 2), array('showdescription' => true, 'completionview' => 1, 'completion' => COMPLETION_TRACKING_AUTOMATIC)); $forumcompleteautocm = get_coursemodule_from_id('forum', $forumcompleteauto->cmid); - $sectionrecord = $DB->get_record('course_sections', $conditions); - course_purge_section_cache($sectionrecord); - rebuild_course_cache($course->id, true, true); + + rebuild_course_cache($course->id, true); return array($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm, $forumcompleteautocm); } diff --git a/lib/db/caches.php b/lib/db/caches.php index 9ebb658a70e..e64739d5502 100644 --- a/lib/db/caches.php +++ b/lib/db/caches.php @@ -221,16 +221,8 @@ $definitions = array( 'simplekeys' => true, 'ttl' => 3600, ), - // Accumulated information about course modules and sections used to print course view page (user-independent). - // Used in functions: - // - course_modinfo::build_course_section_cache() - // - course_modinfo::inner_build_course_cache() - // - get_array_of_activities() - // Reset/update in functions: - // - rebuild_course_cache() - // - course_purge_module_cache() - // - course_purge_section_cache() - // - remove_course_contents(). + // Accumulated information about course modules and sections used to print course view page (user-independed). + // Used in function get_fast_modinfo(), reset in function rebuild_course_cache(). 'coursemodinfo' => array( 'mode' => cache_store::MODE_APPLICATION, 'simplekeys' => true, diff --git a/lib/modinfolib.php b/lib/modinfolib.php index a29078654ff..191e5e062a6 100644 --- a/lib/modinfolib.php +++ b/lib/modinfolib.php @@ -514,7 +514,7 @@ class course_modinfo { ' does not have context. Rebuilding cache for course '. $course->id); // Re-request the course record from DB as well, don't use get_course() here. $course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST); - $coursemodinfo = self::build_course_cache($course, true); + $coursemodinfo = self::build_course_cache($course); break; } } @@ -553,7 +553,6 @@ class course_modinfo { $this->instances[$cm->modname] = array(); } $this->instances[$cm->modname][$cm->instance] = $cm; - ksort($this->instances[$cm->modname]); $this->cms[$cm->id] = $cm; // Reconstruct sections. This works because modules are stored in order @@ -563,8 +562,6 @@ class course_modinfo { $this->sections[$cm->sectionnum][] = $cm->id; } - ksort($this->cms); - ksort($this->instances); // Expand section objects $this->sectioninfo = array(); foreach ($coursemodinfo->sectioncache as $number => $data) { @@ -591,37 +588,23 @@ class course_modinfo { * in some other way.) * * @param stdClass $course Course object (must contain fields - * @param boolean $usecache use cached section info if exists, use true for partial course rebuild * @return array Information about sections, indexed by section number (not id) */ - protected static function build_course_section_cache(\stdClass $course, bool $usecache = false): array { + protected static function build_course_section_cache($course) { global $DB; // Get section data $sections = $DB->get_records('course_sections', array('course' => $course->id), 'section', 'section, id, course, name, summary, summaryformat, sequence, visible, availability'); - $compressedsections = []; - $courseformat = course_get_format($course); - - if ($usecache) { - $cachecoursemodinfo = \cache::make('core', 'coursemodinfo'); - $coursemodinfo = $cachecoursemodinfo->get($course->id); - if ($coursemodinfo !== false) { - $compressedsections = $coursemodinfo->sectioncache; - } - } + $compressedsections = array(); $formatoptionsdef = course_get_format($course)->section_format_options(); // Remove unnecessary data and add availability foreach ($sections as $number => $section) { - $sectioninfocached = isset($compressedsections[$number]); - if ($sectioninfocached) { - continue; - } // Add cached options from course format to $section object foreach ($formatoptionsdef as $key => $option) { if (!empty($option['cache'])) { - $formatoptions = $courseformat->get_format_options($section); + $formatoptions = course_get_format($course)->get_format_options($section); if (!array_key_exists('cachedefault', $option) || $option['cachedefault'] !== $formatoptions[$key]) { $section->$key = $formatoptions[$key]; } @@ -632,7 +615,6 @@ class course_modinfo { section_info::convert_for_section_cache($compressedsections[$number]); } - ksort($compressedsections); return $compressedsections; } @@ -670,20 +652,19 @@ class course_modinfo { * * @param stdClass $course object from DB table course. Must have property 'id' * but preferably should have all cached fields. - * @param boolean $partialrebuild Indicates it's partial course rebuild or not * @return stdClass object with all cached keys of the course plus fields modinfo and sectioncache. * The same object is stored in MUC * @throws moodle_exception if course is not found (if $course object misses some of the * necessary fields it is re-requested from database) */ - public static function build_course_cache(\stdClass $course, bool $partialrebuild = false): \stdClass { + public static function build_course_cache($course) { if (empty($course->id)) { throw new coding_exception('Object $course is missing required property \id\''); } $lock = self::get_course_cache_lock($course->id); try { - return self::inner_build_course_cache($course, $lock, $partialrebuild); + return self::inner_build_course_cache($course, $lock); } finally { $lock->release(); } @@ -694,11 +675,9 @@ class course_modinfo { * * @param stdClass $course object from DB table course * @param \core\lock\lock $lock Lock object - not actually used, just there to indicate you have a lock - * @param bool $partialrebuild Is partial rebuild or not * @return stdClass Course object that has been stored in MUC */ - protected static function inner_build_course_cache(\stdClass $course, \core\lock\lock $lock, - bool $partialrebuild = false): \stdClass { + protected static function inner_build_course_cache($course, \core\lock\lock $lock) { global $DB, $CFG; require_once("{$CFG->dirroot}/course/lib.php"); @@ -714,8 +693,8 @@ class course_modinfo { // This may take time on large courses and it is possible that another user modifies the same course during this process. // Field cacherev stored in both DB and cache will ensure that cached data matches the current course state. $coursemodinfo = new stdClass(); - $coursemodinfo->modinfo = get_array_of_activities($course->id, $partialrebuild); - $coursemodinfo->sectioncache = self::build_course_section_cache($course, $partialrebuild); + $coursemodinfo->modinfo = get_array_of_activities($course->id); + $coursemodinfo->sectioncache = self::build_course_section_cache($course); foreach (self::$cachedfields as $key) { $coursemodinfo->$key = $course->$key; } @@ -2436,22 +2415,9 @@ function get_course_and_cm_from_instance($instanceorid, $modulename, $courseorid * @param int $courseid id of course to rebuild, empty means all * @param boolean $clearonly only clear the cache, gets rebuild automatically on the fly. * Recommended to set to true to avoid unnecessary multiple rebuilding. - * @param boolean $partialrebuild will not delete the whole cache when it's true. - * use course_purge_module_cache() or course_purge_section_cache() must be - * called before when partialrebuild is true. - * use course_purge_module_cache() to invalidate mod cache. - * use course_purge_section_cache() to invalidate section cache. - * - * @return void - * @throws coding_exception */ -function rebuild_course_cache(int $courseid = 0, bool $clearonly = false, bool $partialrebuild = false): void { - global $COURSE, $SITE, $DB; - - if ($courseid == 0 and !empty($partialrebuild)) { - $error = 'partialrebuild only works when a valid course id is provided.'; - throw new coding_exception($error); - } +function rebuild_course_cache($courseid=0, $clearonly=false) { + global $COURSE, $SITE, $DB, $CFG; // Function rebuild_course_cache() can not be called during upgrade unless it's clear only. if (!$clearonly && !upgrade_ensure_not_running(true)) { @@ -2467,10 +2433,7 @@ function rebuild_course_cache(int $courseid = 0, bool $clearonly = false, bool $ if (empty($courseid)) { // Clearing caches for all courses. increment_revision_number('course', 'cacherev', ''); - if (!$partialrebuild) { - $cachecoursemodinfo->purge(); - } - // Clear memory static cache. + $cachecoursemodinfo->purge(); course_modinfo::clear_instance_cache(); // Update global values too. $sitecacherev = $DB->get_field('course', 'cacherev', array('id' => SITEID)); @@ -2483,11 +2446,7 @@ function rebuild_course_cache(int $courseid = 0, bool $clearonly = false, bool $ } else { // Clearing cache for one course, make sure it is deleted from user request cache as well. increment_revision_number('course', 'cacherev', 'id = :id', array('id' => $courseid)); - if (!$partialrebuild) { - // Purge all course modinfo. - $cachecoursemodinfo->delete($courseid); - } - // Clear memory static cache. + $cachecoursemodinfo->delete($courseid); course_modinfo::clear_instance_cache($courseid); // Update global values too. if ($courseid == $COURSE->id || $courseid == $SITE->id) { @@ -2512,13 +2471,10 @@ function rebuild_course_cache(int $courseid = 0, bool $clearonly = false, bool $ core_php_time_limit::raise(); // this could take a while! MDL-10954 } - $fields = 'id,' . join(',', course_modinfo::$cachedfields); - $sort = ''; - $rs = $DB->get_recordset("course", $select, $sort, $fields); - + $rs = $DB->get_recordset("course", $select,'','id,'.join(',', course_modinfo::$cachedfields)); // Rebuild cache for each course. foreach ($rs as $course) { - course_modinfo::build_course_cache($course, $partialrebuild); + course_modinfo::build_course_cache($course); } $rs->close(); } diff --git a/lib/tests/modinfolib_test.php b/lib/tests/modinfolib_test.php index 97fa7cd76c6..0f6222871c2 100644 --- a/lib/tests/modinfolib_test.php +++ b/lib/tests/modinfolib_test.php @@ -365,7 +365,7 @@ class modinfolib_test extends advanced_testcase { $this->assertEquals($USER->id, $modinfo->userid); $this->assertEquals(array(0 => array($forum0->cmid, $assign0->cmid), 1 => array($forum1->cmid, $assign1->cmid, $page1->cmid), 3 => array($page3->cmid)), $modinfo->sections); - $this->assertEquals(array('assign', 'forum', 'page'), array_keys($modinfo->instances)); + $this->assertEquals(array('forum', 'assign', 'page'), array_keys($modinfo->instances)); $this->assertEquals(array($assign0->id, $assign1->id), array_keys($modinfo->instances['assign'])); $this->assertEquals(array($forum0->id, $forum1->id), array_keys($modinfo->instances['forum'])); $this->assertEquals(array($page1->id, $page3->id), array_keys($modinfo->instances['page'])); diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 419c2a20cbd..bb189e75f2a 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -26,8 +26,7 @@ information provided here is intended especially for developers. * The completion_info function display_help_icon() which returned the 'Your progress' help icon has been deprecated and should no longer be used. * The completion_info function print_help_icon() which has been deprecated since Moodle 2.0 should no longer be used. -* @babel/polyfill has been removed in favour of corejs@3. -* A new parameter $partialrebuild has been added to the rebuild_course_cache. +* @babel/polyfill has been removed in favour of corejs@3 * A new parameter $isbulkupdate has been added to the following functions: - grade_category::update() - grade_category::insert() diff --git a/mod/chat/lib.php b/mod/chat/lib.php index b6332a1d4bc..06abf29fc9f 100644 --- a/mod/chat/lib.php +++ b/mod/chat/lib.php @@ -121,8 +121,7 @@ function padding($n) { * @return int */ function chat_add_instance($chat) { - global $DB, $CFG; - require_once($CFG->dirroot . '/course/lib.php'); + global $DB; $chat->timemodified = time(); $chat->chattime = chat_calculate_next_chat_time($chat->schedule, $chat->chattime); @@ -707,13 +706,10 @@ function chat_update_chat_times($chatid=0) { $chat->chattime = chat_calculate_next_chat_time($chat->schedule, $chat->chattime); if ($originalchattime != $chat->chattime) { $courseids[] = $chat->course; - $DB->update_record("chat", $chat); - - $cm = get_coursemodule_from_instance('chat', $chat->id, $chat->course); - course_purge_module_cache($cm); } - + $DB->update_record("chat", $chat); $event = new stdClass(); // Update calendar too. + $cond = "modulename='chat' AND eventtype = :eventtype AND instance = :chatid AND timestart <> :chattime"; $params = ['chattime' => $chat->chattime, 'eventtype' => CHAT_EVENT_TYPE_CHATTIME, 'chatid' => $chat->id]; @@ -727,7 +723,7 @@ function chat_update_chat_times($chatid=0) { $courseids = array_unique($courseids); foreach ($courseids as $courseid) { - rebuild_course_cache($courseid, true, true); + rebuild_course_cache($courseid, true); } } diff --git a/mod/lti/locallib.php b/mod/lti/locallib.php index 03dc710144a..b14a4cdddb7 100644 --- a/mod/lti/locallib.php +++ b/mod/lti/locallib.php @@ -2833,23 +2833,14 @@ function lti_update_type($type, $config) { } require_once($CFG->libdir.'/modinfolib.php'); if ($clearcache) { - $sql = "SELECT cm.id, cm.course - FROM {course_modules} cm - JOIN {modules} m ON cm.module = m.id - JOIN {lti} l ON l.course = cm.course - WHERE m.name = :name AND l.typeid = :typeid"; + $sql = "SELECT DISTINCT course + FROM {lti} + WHERE typeid = ?"; - $rs = $DB->get_recordset_sql($sql, ['name' => 'lti', 'typeid' => $type->id]); + $courses = $DB->get_fieldset_sql($sql, array($type->id)); - $courseids = []; - foreach ($rs as $record) { - $courseids[] = $record->course; - course_purge_module_cache($record); - } - $rs->close(); - $courseids = array_unique($courseids); - foreach ($courseids as $courseid) { - rebuild_course_cache($courseid, false, true); + foreach ($courses as $courseid) { + rebuild_course_cache($courseid, true); } } }