From 2affc87d2bd8bf495de079db751c2b2254181c50 Mon Sep 17 00:00:00 2001 From: Dmitrii Metelkin Date: Mon, 15 Mar 2021 12:50:35 +1100 Subject: [PATCH] MDL-66667 course: add course image cache --- course/classes/cache/course_image.php | 102 ++++++++++ .../external/course_summary_exporter.php | 22 +-- course/lib.php | 3 + course/tests/course_image_cache_test.php | 176 ++++++++++++++++++ course/tests/course_summary_exporter_test.php | 73 ++++++++ course/tests/fixtures/image.jpg | Bin 0 -> 7237 bytes lang/en/cache.php | 1 + lib/db/caches.php | 9 + 8 files changed, 371 insertions(+), 15 deletions(-) create mode 100644 course/classes/cache/course_image.php create mode 100644 course/tests/course_image_cache_test.php create mode 100644 course/tests/course_summary_exporter_test.php create mode 100755 course/tests/fixtures/image.jpg diff --git a/course/classes/cache/course_image.php b/course/classes/cache/course_image.php new file mode 100644 index 00000000000..e01b31c93ec --- /dev/null +++ b/course/classes/cache/course_image.php @@ -0,0 +1,102 @@ +. + +namespace core_course\cache; + +use cache_data_source; +use cache_definition; +use moodle_url; +use core_course_list_element; + +/** + * Class to describe cache data source for course image. + * + * @package core + * @subpackage course + * @author Dmitrii Metelkin + * @copyright 2021 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class course_image implements cache_data_source { + + /** @var course_image */ + protected static $instance = null; + + /** + * Returns an instance of the data source class that the cache can use for loading data using the other methods + * specified by this interface. + * + * @param cache_definition $definition + * @return \core_course\cache\course_image + */ + public static function get_instance_for_cache(cache_definition $definition): course_image { + if (is_null(self::$instance)) { + self::$instance = new course_image(); + } + return self::$instance; + } + + /** + * Loads the data for the key provided ready formatted for caching. + * + * @param string|int $key The key to load. + * @return string|bool Returns course image url as a string or false if the image is not exist + */ + public function load_for_cache($key) { + $course = get_fast_modinfo($key)->get_course(); + return $this->get_image_url_from_overview_files($course); + } + + /** + * Returns image URL from course overview files. + * + * @param \stdClass $course Course object. + * @return null|string Image URL or null if it's not exists. + */ + protected function get_image_url_from_overview_files(\stdClass $course): ?string { + $courseinlist = new core_course_list_element($course); + foreach ($courseinlist->get_course_overviewfiles() as $file) { + if ($file->is_valid_image()) { + return moodle_url::make_pluginfile_url( + $file->get_contextid(), + $file->get_component(), + $file->get_filearea(), + null, + $file->get_filepath(), + $file->get_filename() + )->out(); + } + } + + // Returning null if no image found to let it be cached + // as false is what cache API returns then a data is not found in cache. + return null; + } + + /** + * Loads several keys for the cache. + * + * @param array $keys An array of keys each of which will be string|int. + * @return array An array of matching data items. + */ + public function load_many_for_cache(array $keys): array { + $records = []; + foreach ($keys as $key) { + $records[$key] = $this->load_for_cache($key); + } + return $records; + } +} diff --git a/course/classes/external/course_summary_exporter.php b/course/classes/external/course_summary_exporter.php index 65471f7298d..3ac98879ac5 100644 --- a/course/classes/external/course_summary_exporter.php +++ b/course/classes/external/course_summary_exporter.php @@ -165,24 +165,16 @@ class course_summary_exporter extends \core\external\exporter { * Get the course image if added to course. * * @param object $course - * @return string url of course image + * @return string|false url of course image or false if it's not exist. */ public static function get_course_image($course) { - global $CFG; - $courseinlist = new \core_course_list_element($course); - foreach ($courseinlist->get_course_overviewfiles() as $file) { - if ($file->is_valid_image()) { - $pathcomponents = [ - '/pluginfile.php', - $file->get_contextid(), - $file->get_component(), - $file->get_filearea() . $file->get_filepath() . $file->get_filename() - ]; - $path = implode('/', $pathcomponents); - return (new moodle_url($path))->out(); - } + $image = \cache::make('core', 'course_image')->get($course->id); + + if (is_null($image)) { + $image = false; } - return false; + + return $image; } /** diff --git a/course/lib.php b/course/lib.php index d1b676ef53c..098162d1a55 100644 --- a/course/lib.php +++ b/course/lib.php @@ -2551,6 +2551,9 @@ function update_course($data, $editoroptions = NULL) { // make sure the modinfo cache is reset rebuild_course_cache($data->id); + // Purge course image cache in case if course image has been updated. + \cache::make('core', 'course_image')->delete($data->id); + // update course format options with full course data course_get_format($data->id)->update_course_format_options($data, $oldcourse); diff --git a/course/tests/course_image_cache_test.php b/course/tests/course_image_cache_test.php new file mode 100644 index 00000000000..f8f98596e49 --- /dev/null +++ b/course/tests/course_image_cache_test.php @@ -0,0 +1,176 @@ +. + +namespace tests\core_course; + +use context_user; +use context_course; +use ReflectionMethod; +use cache_definition; +use core_course\cache\course_image; + +/** + * Functional test for class course_image + * + * @package core + * @subpackage course + * @author Dmitrii Metelkin + * @copyright 2021 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class course_image_cache_testcase extends \advanced_testcase { + + /** + * Initial setup. + */ + protected function setUp(): void { + global $CFG; + + parent::setUp(); + $this->resetAfterTest(); + $this->setAdminUser(); + + // Allow multiple overview files. + $CFG->courseoverviewfileslimit = 3; + } + + /** + * Helper method to create a draft area for current user and fills it with fake files + * + * @param array $files array of files that need to be added to filearea, filename => filecontents + * @return int draftid for the filearea + */ + protected function fill_draft_area(array $files): int { + global $USER; + + $draftid = file_get_unused_draft_itemid(); + foreach ($files as $filename => $filecontents) { + // Add actual file there. + $filerecord = [ + 'component' => 'user', + 'filearea' => 'draft', + 'contextid' => context_user::instance($USER->id)->id, 'itemid' => $draftid, + 'filename' => $filename, 'filepath' => '/' + ]; + $fs = get_file_storage(); + $fs->create_file_from_string($filerecord, $filecontents); + } + return $draftid; + } + + /** + * A helper method to generate expected file URL. + * + * @param \stdClass $course Course object. + * @param string $filename File name. + * @return string + */ + protected function build_expected_course_image_url(\stdClass $course, string $filename): string { + $contextid = context_course::instance($course->id)->id; + return 'https://www.example.com/moodle/pluginfile.php/' . $contextid. '/course/overviewfiles/' . $filename; + } + + /** + * Test exception if try to get an image for non existing course. + */ + public function test_getting_data_if_course_is_not_exist() { + $this->expectException('dml_missing_record_exception'); + $this->expectExceptionMessageMatches("/Can't find data record in database table course./"); + $this->assertFalse(\cache::make('core', 'course_image')->get(999)); + } + + /** + * Test get_image_url_from_overview_files when no summary files in the course. + */ + public function test_get_image_url_from_overview_files_return_null_if_no_summary_files_in_the_course() { + $method = new ReflectionMethod(course_image::class, 'get_image_url_from_overview_files'); + $cache = course_image::get_instance_for_cache(new cache_definition()); + $method->setAccessible(true); + + // Create course without files. + $course = $this->getDataGenerator()->create_course(); + $this->assertNull($method->invokeArgs($cache, [$course])); + } + + /** + * Test get_image_url_from_overview_files when no summary images in the course. + */ + public function test_get_image_url_from_overview_files_returns_null_if_no_summary_images_in_the_course() { + $method = new ReflectionMethod(course_image::class, 'get_image_url_from_overview_files'); + $cache = course_image::get_instance_for_cache(new cache_definition()); + $method->setAccessible(true); + + // Create course without image files. + $draftid2 = $this->fill_draft_area(['filename2.zip' => 'Test file contents2']); + $course2 = $this->getDataGenerator()->create_course(['overviewfiles_filemanager' => $draftid2]); + $this->assertNull($method->invokeArgs($cache, [$course2])); + } + + /** + * Test get_image_url_from_overview_files when no summary images in the course. + */ + public function test_get_image_url_from_overview_files_returns_url_if_there_is_a_summary_image() { + $method = new ReflectionMethod(course_image::class, 'get_image_url_from_overview_files'); + $cache = course_image::get_instance_for_cache(new cache_definition()); + $method->setAccessible(true); + + // Create course without one image. + $draftid1 = $this->fill_draft_area(['filename1.jpg' => file_get_contents(__DIR__ . '/fixtures/image.jpg')]); + $course1 = $this->getDataGenerator()->create_course(['overviewfiles_filemanager' => $draftid1]); + $expected = $this->build_expected_course_image_url($course1, 'filename1.jpg'); + $this->assertEquals($expected, $method->invokeArgs($cache, [$course1])); + } + + /** + * Test get_image_url_from_overview_files when several summary images in the course. + */ + public function test_get_image_url_from_overview_files_returns_url_of_the_first_image_if_there_are_many_summary_images() { + $method = new ReflectionMethod(course_image::class, 'get_image_url_from_overview_files'); + $cache = course_image::get_instance_for_cache(new cache_definition()); + $method->setAccessible(true); + + // Create course with two image files. + $draftid1 = $this->fill_draft_area([ + 'filename1.jpg' => file_get_contents(__DIR__ . '/fixtures/image.jpg'), + 'filename2.jpg' => file_get_contents(__DIR__ . '/fixtures/image.jpg'), + ]); + $course1 = $this->getDataGenerator()->create_course(['overviewfiles_filemanager' => $draftid1]); + + $expected = $this->build_expected_course_image_url($course1, 'filename1.jpg'); + $this->assertEquals($expected, $method->invokeArgs($cache, [$course1])); + } + + /** + * Test get_image_url_from_overview_files when several summary files in the course. + */ + public function test_get_image_url_from_overview_files_returns_url_of_the_first_image_if_there_are_many_summary_files() { + $method = new ReflectionMethod(course_image::class, 'get_image_url_from_overview_files'); + $cache = course_image::get_instance_for_cache(new cache_definition()); + $method->setAccessible(true); + + // Create course with two image files and one zip file. + $draftid1 = $this->fill_draft_area([ + 'filename1.zip' => 'Test file contents2', + 'filename2.jpg' => file_get_contents(__DIR__ . '/fixtures/image.jpg'), + 'filename3.jpg' => file_get_contents(__DIR__ . '/fixtures/image.jpg'), + ]); + $course1 = $this->getDataGenerator()->create_course(['overviewfiles_filemanager' => $draftid1]); + + $expected = $this->build_expected_course_image_url($course1, 'filename2.jpg'); + $this->assertEquals($expected, $method->invokeArgs($cache, [$course1])); + } + +} diff --git a/course/tests/course_summary_exporter_test.php b/course/tests/course_summary_exporter_test.php new file mode 100644 index 00000000000..7f6503d2a95 --- /dev/null +++ b/course/tests/course_summary_exporter_test.php @@ -0,0 +1,73 @@ +. + +namespace tests\core_course; + +use core_course\external\course_summary_exporter; +use context_user; +use context_course; + +/** + * Functional test for class course_summary_exporter + * + * @package core + * @subpackage course + * @author Dmitrii Metelkin + * @copyright 2021 Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class course_summary_exporter_testcase extends \advanced_testcase { + + /** + * Test that if no course overview images uploaded get_course_image returns false. + */ + public function test_get_course_image_when_no_overview_images_uploaded() { + $this->resetAfterTest(true); + $this->setAdminUser(); + $course = $this->getDataGenerator()->create_course(); + + $this->assertFalse(course_summary_exporter::get_course_image($course)); + } + + /** + * Test that if course overview images uploaded get_course_image returns an image URL. + */ + public function test_get_course_image_when_overview_images_are_uploaded() { + global $USER; + + $this->resetAfterTest(true); + $this->setAdminUser(); + + $draftid = file_get_unused_draft_itemid(); + $filerecord = [ + 'component' => 'user', + 'filearea' => 'draft', + 'contextid' => context_user::instance($USER->id)->id, + 'itemid' => $draftid, + 'filename' => 'image.jpg', + 'filepath' => '/', + ]; + $fs = get_file_storage(); + $fs->create_file_from_string($filerecord, file_get_contents(__DIR__ . '/fixtures/image.jpg')); + $course = $this->getDataGenerator()->create_course(['overviewfiles_filemanager' => $draftid]); + $coursecontext = context_course::instance($course->id); + + $expected = 'https://www.example.com/moodle/pluginfile.php/' . $coursecontext->id . '/course/overviewfiles/image.jpg'; + $actual = course_summary_exporter::get_course_image($course); + $this->assertSame($expected, $actual); + } + +} diff --git a/course/tests/fixtures/image.jpg b/course/tests/fixtures/image.jpg new file mode 100755 index 0000000000000000000000000000000000000000..91cc0d3cfe428b81358758bcd5f7792aaac7e7fa GIT binary patch literal 7237 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 literal 0 HcmV?d00001 diff --git a/lang/en/cache.php b/lang/en/cache.php index a0ff0c3cbb4..d4eac728c9e 100644 --- a/lang/en/cache.php +++ b/lang/en/cache.php @@ -49,6 +49,7 @@ $string['cachedef_coursecattree'] = 'Course categories tree'; $string['cachedef_coursecompletion'] = 'Course completion status'; $string['cachedef_coursecontacts'] = 'List of course contacts'; $string['cachedef_coursemodinfo'] = 'Accumulated information about modules and sections for each course'; +$string['cachedef_course_image'] = 'Course images'; $string['cachedef_course_user_dates'] = 'The user dates for courses set to relative dates mode'; $string['cachedef_completion'] = 'Activity completion status'; $string['cachedef_databasemeta'] = 'Database meta information'; diff --git a/lib/db/caches.php b/lib/db/caches.php index e7b352dad18..fac98b7aa63 100644 --- a/lib/db/caches.php +++ b/lib/db/caches.php @@ -484,4 +484,13 @@ $definitions = array( 'staticacceleration' => true, 'staticaccelerationsize' => 100 ], + + // Course image cache. + 'course_image' => [ + 'mode' => cache_store::MODE_APPLICATION, + 'simplekeys' => true, + 'simpledata' => true, + 'staticacceleration' => true, + 'datasource' => '\core_course\cache\course_image', + ], );