3030 * @author Dmitrii Metelkin <dmitriim@catalyst-au.net>
3131 * @copyright 2021 Catalyst IT
3232 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33+ * @covers \core_course\cache\course_image
3334 */
3435final class course_image_cache_test extends \advanced_testcase {
3536
@@ -76,11 +77,13 @@ protected function fill_draft_area(array $files): int {
7677 *
7778 * @param \stdClass $course Course object.
7879 * @param string $filename File name.
80+ * @param int $timemodified File timemodified timestamp for cache-busting.
7981 * @return string
8082 */
81- protected function build_expected_course_image_url (\stdClass $ course , string $ filename ): string {
83+ protected function build_expected_course_image_url (\stdClass $ course , string $ filename, int $ timemodified ): string {
8284 $ contextid = context_course::instance ($ course ->id )->id ;
83- return 'https://www.example.com/moodle/pluginfile.php/ ' . $ contextid . '/course/overviewfiles/ ' . $ filename ;
85+ return 'https://www.example.com/moodle/pluginfile.php/ ' . $ contextid .
86+ '/course/overviewfiles/ ' . $ filename . '?oid= ' . $ timemodified ;
8487 }
8588
8689 /**
@@ -129,7 +132,9 @@ public function test_get_image_url_from_overview_files_returns_url_if_there_is_a
129132 'filename1.jpg ' => file_get_contents (self ::get_fixture_path (__NAMESPACE__ , 'image.jpg ' )),
130133 ]);
131134 $ course1 = $ this ->getDataGenerator ()->create_course (['overviewfiles_filemanager ' => $ draftid1 ]);
132- $ expected = $ this ->build_expected_course_image_url ($ course1 , 'filename1.jpg ' );
135+ $ contextid = context_course::instance ($ course1 ->id )->id ;
136+ $ file = get_file_storage ()->get_file ($ contextid , 'course ' , 'overviewfiles ' , 0 , '/ ' , 'filename1.jpg ' );
137+ $ expected = $ this ->build_expected_course_image_url ($ course1 , 'filename1.jpg ' , $ file ->get_timemodified ());
133138 $ this ->assertEquals ($ expected , $ method ->invokeArgs ($ cache , [$ course1 ]));
134139 }
135140
@@ -146,8 +151,9 @@ public function test_get_image_url_from_overview_files_returns_url_of_the_first_
146151 'filename2.jpg ' => file_get_contents (self ::get_fixture_path (__NAMESPACE__ , 'image.jpg ' )),
147152 ]);
148153 $ course1 = $ this ->getDataGenerator ()->create_course (['overviewfiles_filemanager ' => $ draftid1 ]);
149-
150- $ expected = $ this ->build_expected_course_image_url ($ course1 , 'filename1.jpg ' );
154+ $ contextid = context_course::instance ($ course1 ->id )->id ;
155+ $ file = get_file_storage ()->get_file ($ contextid , 'course ' , 'overviewfiles ' , 0 , '/ ' , 'filename1.jpg ' );
156+ $ expected = $ this ->build_expected_course_image_url ($ course1 , 'filename1.jpg ' , $ file ->get_timemodified ());
151157 $ this ->assertEquals ($ expected , $ method ->invokeArgs ($ cache , [$ course1 ]));
152158 }
153159
@@ -165,9 +171,44 @@ public function test_get_image_url_from_overview_files_returns_url_of_the_first_
165171 'filename3.jpg ' => file_get_contents (self ::get_fixture_path (__NAMESPACE__ , 'image.jpg ' )),
166172 ]);
167173 $ course1 = $ this ->getDataGenerator ()->create_course (['overviewfiles_filemanager ' => $ draftid1 ]);
168-
169- $ expected = $ this ->build_expected_course_image_url ($ course1 , 'filename2.jpg ' );
174+ $ contextid = context_course::instance ($ course1 ->id )->id ;
175+ $ file = get_file_storage ()->get_file ($ contextid , 'course ' , 'overviewfiles ' , 0 , '/ ' , 'filename2.jpg ' );
176+ $ expected = $ this ->build_expected_course_image_url ($ course1 , 'filename2.jpg ' , $ file ->get_timemodified ());
170177 $ this ->assertEquals ($ expected , $ method ->invokeArgs ($ cache , [$ course1 ]));
171178 }
172179
180+ /**
181+ * Test that replacing a course image with a file of the same name produces a different URL.
182+ */
183+ public function test_get_image_url_changes_when_file_replaced_with_same_name (): void {
184+ $ method = new ReflectionMethod (course_image::class, 'get_image_url_from_overview_files ' );
185+ $ cache = course_image::get_instance_for_cache (new definition ());
186+ $ imagecontent = file_get_contents (self ::get_fixture_path (__NAMESPACE__ , 'image.jpg ' ));
187+
188+ // Create course with initial image.
189+ $ draftid = $ this ->fill_draft_area (['image.jpg ' => $ imagecontent ]);
190+ $ course = $ this ->getDataGenerator ()->create_course (['overviewfiles_filemanager ' => $ draftid ]);
191+
192+ $ contextid = context_course::instance ($ course ->id )->id ;
193+ $ fs = get_file_storage ();
194+ $ originalfile = $ fs ->get_file ($ contextid , 'course ' , 'overviewfiles ' , 0 , '/ ' , 'image.jpg ' );
195+ $ url1 = $ method ->invokeArgs ($ cache , [$ course ])->out_as_local_url ();
196+
197+ // Simulate file replacement with same name: delete original, re-create with a later timemodified.
198+ $ newrecord = [
199+ 'contextid ' => $ contextid ,
200+ 'component ' => 'course ' ,
201+ 'filearea ' => 'overviewfiles ' ,
202+ 'itemid ' => 0 ,
203+ 'filepath ' => '/ ' ,
204+ 'filename ' => 'image.jpg ' ,
205+ 'timemodified ' => $ originalfile ->get_timemodified () + 1 ,
206+ ];
207+ $ originalfile ->delete ();
208+ $ fs ->create_file_from_string ($ newrecord , $ imagecontent );
209+
210+ $ url2 = $ method ->invokeArgs ($ cache , [$ course ])->out_as_local_url ();
211+
212+ $ this ->assertNotEquals ($ url1 , $ url2 , 'URL must change when course image is replaced with a file of the same name. ' );
213+ }
173214}
0 commit comments