forked from moodle/moodle
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MDL-66667 course: add course image cache
- Loading branch information
Showing
8 changed files
with
371 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
<?php | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
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 <dmitriim@catalyst-au.net> | ||
* @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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
<?php | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
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 <dmitriim@catalyst-au.net> | ||
* @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])); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<?php | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
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 <dmitriim@catalyst-au.net> | ||
* @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); | ||
} | ||
|
||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.