From 1716e2f2cda46ec0523274eb2e21b7b9ff2b6567 Mon Sep 17 00:00:00 2001 From: ferran Date: Wed, 30 Oct 2024 15:10:59 +0100 Subject: [PATCH] MDL-83185 core_courseformat: Allow global state cache invalidation --- .upgradenotes/MDL-83185-2024103109015923.yml | 8 ++ course/format/classes/base.php | 14 ++++ course/format/tests/base_test.php | 78 ++++++++++++++++++++ lib/db/caches.php | 3 + 4 files changed, 103 insertions(+) create mode 100644 .upgradenotes/MDL-83185-2024103109015923.yml diff --git a/.upgradenotes/MDL-83185-2024103109015923.yml b/.upgradenotes/MDL-83185-2024103109015923.yml new file mode 100644 index 00000000000..45cb74fae99 --- /dev/null +++ b/.upgradenotes/MDL-83185-2024103109015923.yml @@ -0,0 +1,8 @@ +issueNumber: MDL-83185 +notes: + core_courseformat: + - message: >- + Add core_courseformat\base::invalidate_all_session_caches to reset course editor cache for all users when course is changed. + This method can be used as an alternative to core_courseformat\base::session_cache_reset for resetting the cache for the current user + in case the change in the course should be reflected for all users. + type: improved diff --git a/course/format/classes/base.php b/course/format/classes/base.php index 663aba3a0c6..85dc0c6f7a2 100644 --- a/course/format/classes/base.php +++ b/course/format/classes/base.php @@ -283,6 +283,20 @@ abstract class base { return self::session_cache_reset($course); } + /** + * Prune a course state cache for all open sessions. + * + * Most course edits does not require to invalidate the cache for all users + * because the cache relies on the course cacherev value. However, there are + * actions like editing the groups that do not change the course cacherev. + * + * @param \stdClass $course + * @return void + */ + public static function invalidate_all_session_caches_for_course(stdClass $course): void { + \cache_helper::invalidate_by_event('changesincoursestate', [$course->id]); + } + /** * Returns the format name used by this course * diff --git a/course/format/tests/base_test.php b/course/format/tests/base_test.php index a0f9b2238e4..859355ca2b4 100644 --- a/course/format/tests/base_test.php +++ b/course/format/tests/base_test.php @@ -996,6 +996,84 @@ class base_test extends advanced_testcase { $this->assertFalse($format->is_section_visible($modinfostudent->get_section_info(1))); $this->assertFalse($format->is_section_visible($modinfostudent->get_section_info(2))); } + + /** + * Test can_sections_be_removed_from_navigation(). + * + * @covers ::session_cache + * @covers ::session_cache_reset + * @covers ::session_cache_reset_all + * @covers ::invalidate_all_session_caches_for_course + */ + public function test_session_caches_methods(): void { + global $DB; + + $this->resetAfterTest(); + $generator = $this->getDataGenerator(); + + $course1 = $generator->create_course(['format' => 'topics']); + $course2 = $generator->create_course(['format' => 'topics']); + + // Force some cacherev to the course. + $course1->cacherev = 12345; + $course2->cacherev = 67890; + $DB->set_field('course', 'cacherev', $course1->cacherev, ['id' => $course1->id]); + $DB->set_field('course', 'cacherev', $course2->cacherev, ['id' => $course2->id]); + + $teacher = $generator->create_and_enrol($course1, 'editingteacher'); + $generator->enrol_user($teacher->id, $course2->id, 'editingteacher'); + $this->setUser($teacher); + + // The cache key uses time() as hash. To not wait a second between calls we fake an initial value. + $statecache = cache::make('core', 'courseeditorstate'); + $statecache->set($course1->id, $course1->cacherev . '_11111'); + $statecache->set($course2->id, $course2->cacherev . '_22222'); + + $course1cachekey = \core_courseformat\base::session_cache($course1); + + // Validate the method returns the same value when called twice. + $course1cachekeyagain = \core_courseformat\base::session_cache($course1); + $this->assertEquals($course1cachekey, $course1cachekeyagain); + + // Validate other course has a diferent cache key. + $course2cachekey = \core_courseformat\base::session_cache($course2); + $this->assertNotEquals($course1cachekey, $course2cachekey); + + // Reset the specific course cache. + \core_courseformat\base::session_cache_reset($course1); + + $resetcachekey = \core_courseformat\base::session_cache($course1); + $this->assertNotEquals($course1cachekey, $resetcachekey); + + $reset2cachekey = \core_courseformat\base::session_cache($course2); + $this->assertEquals($course2cachekey, $reset2cachekey); + + // Return to the initial value. + $statecache->set($course1->id, $course1->cacherev . '_11111'); + $statecache->set($course2->id, $course2->cacherev . '_22222'); + + // Reset all user course caches. + \core_courseformat\base::session_cache_reset_all(); + + $resetallcachekey = \core_courseformat\base::session_cache($course1); + $this->assertNotEquals($course1cachekey, $resetallcachekey); + + $resetall2cachekey = \core_courseformat\base::session_cache($course2); + $this->assertNotEquals($course2cachekey, $resetall2cachekey); + + // Return to the initial value. + $statecache->set($course1->id, $course1->cacherev . '_11111'); + $statecache->set($course2->id, $course2->cacherev . '_22222'); + + // Invalidate cache on course 1. + \core_courseformat\base::invalidate_all_session_caches_for_course($course1); + + $invalidatecachekey = \core_courseformat\base::session_cache($course1); + $this->assertNotEquals($course1cachekey, $invalidatecachekey); + + $invalidate2cachekey = \core_courseformat\base::session_cache($course2); + $this->assertEquals($course2cachekey, $invalidate2cachekey); + } } /** diff --git a/lib/db/caches.php b/lib/db/caches.php index bf06bd8d60a..5451ce3d9a7 100644 --- a/lib/db/caches.php +++ b/lib/db/caches.php @@ -239,6 +239,9 @@ $definitions = array( 'mode' => cache_store::MODE_SESSION, 'simplekeys' => true, 'simpledata' => true, + 'invalidationevents' => [ + 'changesincoursestate', + ], ], // Course actions instances cache. 'courseactionsinstances' => [