From 27be439eb7952ccc8f8eb9509041d2a7c865ca1f Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Fri, 28 Jan 2022 22:01:58 +0100 Subject: [PATCH] MDL-73708 course: Return geo pattern generated course images via WS --- course/externallib.php | 8 +++++ course/tests/externallib_test.php | 47 ++++++++++++++++++++++-------- course/upgrade.txt | 2 ++ enrol/externallib.php | 9 +++++- enrol/tests/externallib_test.php | 4 +++ enrol/upgrade.txt | 2 ++ lib/classes/geopattern.php | 1 - lib/filelib.php | 14 ++++++++- lib/outputrenderers.php | 24 +++++++++++++++ lib/tests/outputrenderers_test.php | 47 ++++++++++++++++++++++++++++++ 10 files changed, 143 insertions(+), 15 deletions(-) create mode 100644 lib/tests/outputrenderers_test.php diff --git a/course/externallib.php b/course/externallib.php index 84f961f14b4..550cc920e24 100644 --- a/course/externallib.php +++ b/course/externallib.php @@ -2434,6 +2434,7 @@ class core_course_external extends external_api { * @since Moodle 3.2 */ protected static function get_course_public_information(core_course_list_element $course, $coursecontext) { + global $OUTPUT; static $categoriescache = array(); @@ -2522,6 +2523,12 @@ class core_course_external extends external_api { } } + $courseimage = \core_course\external\course_summary_exporter::get_course_image($course); + if (!$courseimage) { + $courseimage = $OUTPUT->get_generated_url_for_course($coursecontext); + } + $coursereturns['courseimage'] = $courseimage; + return $coursereturns; } @@ -2639,6 +2646,7 @@ class core_course_external extends external_api { 'fullname' => new external_value(PARAM_RAW, 'course full name'), 'displayname' => new external_value(PARAM_RAW, 'course display name'), 'shortname' => new external_value(PARAM_RAW, 'course short name'), + 'courseimage' => new external_value(PARAM_URL, 'Course image', VALUE_OPTIONAL), 'categoryid' => new external_value(PARAM_INT, 'category id'), 'categoryname' => new external_value(PARAM_RAW, 'category name'), 'sortorder' => new external_value(PARAM_INT, 'Sort order in the category', VALUE_OPTIONAL), diff --git a/course/tests/externallib_test.php b/course/tests/externallib_test.php index a36a867ce5e..79a7c5104be 100644 --- a/course/tests/externallib_test.php +++ b/course/tests/externallib_test.php @@ -2694,9 +2694,11 @@ class externallib_test extends externallib_advanced_testcase { * Test get_courses_by_fields */ public function test_get_courses_by_field() { - global $DB; + global $DB, $USER; $this->resetAfterTest(true); + $this->setAdminUser(); + $category1 = self::getDataGenerator()->create_category(array('name' => 'Cat 1')); $category2 = self::getDataGenerator()->create_category(array('parent' => $category1->id)); $course1 = self::getDataGenerator()->create_course( @@ -2707,7 +2709,25 @@ class externallib_test extends externallib_advanced_testcase { 'categoryid' => $fieldcategory->get('id')]; $field = self::getDataGenerator()->create_custom_field($customfield); $customfieldvalue = ['shortname' => 'test', 'value' => 'Test value']; - $course2 = self::getDataGenerator()->create_course(array('visible' => 0, 'category' => $category2->id, 'idnumber' => 'i2', 'customfields' => [$customfieldvalue])); + // Create course image. + $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_pathname($filerecord, __DIR__ . '/fixtures/image.jpg'); + $course2 = self::getDataGenerator()->create_course([ + 'visible' => 0, + 'category' => $category2->id, + 'idnumber' => 'i2', + 'customfields' => [$customfieldvalue], + 'overviewfiles_filemanager' => $draftid + ]); $student1 = self::getDataGenerator()->create_user(); $user1 = self::getDataGenerator()->create_user(); @@ -2721,16 +2741,16 @@ class externallib_test extends externallib_advanced_testcase { $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); $this->assertCount(3, $result['courses']); // Expect to receive all the fields. - $this->assertCount(40, $result['courses'][0]); - $this->assertCount(41, $result['courses'][1]); // One more field because is not the site course. - $this->assertCount(41, $result['courses'][2]); // One more field because is not the site course. + $this->assertCount(41, $result['courses'][0]); + $this->assertCount(42, $result['courses'][1]); // One more field because is not the site course. + $this->assertCount(42, $result['courses'][2]); // One more field because is not the site course. $result = core_course_external::get_courses_by_field('id', $course1->id); $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); $this->assertCount(1, $result['courses']); $this->assertEquals($course1->id, $result['courses'][0]['id']); // Expect to receive all the fields. - $this->assertCount(41, $result['courses'][0]); + $this->assertCount(42, $result['courses'][0]); // Check default values for course format topics. $this->assertCount(3, $result['courses'][0]['courseformatoptions']); foreach ($result['courses'][0]['courseformatoptions'] as $option) { @@ -2747,6 +2767,7 @@ class externallib_test extends externallib_advanced_testcase { default: } } + $this->assertStringContainsString('/course/generated', $result['courses'][0]['courseimage']); $result = core_course_external::get_courses_by_field('id', $course2->id); $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); @@ -2760,6 +2781,7 @@ class externallib_test extends externallib_advanced_testcase { 'value' => $customfieldvalue['value'], 'valueraw' => $customfieldvalue['value'], ], $result['courses'][0]['customfields'][0]); + $this->assertStringContainsString('/course/overviewfiles', $result['courses'][0]['courseimage']); $result = core_course_external::get_courses_by_field('ids', "$course1->id,$course2->id"); $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); @@ -2797,15 +2819,15 @@ class externallib_test extends externallib_advanced_testcase { $result = core_course_external::get_courses_by_field(); $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); $this->assertCount(2, $result['courses']); - $this->assertCount(33, $result['courses'][0]); - $this->assertCount(34, $result['courses'][1]); // One field more (course format options), not present in site course. + $this->assertCount(34, $result['courses'][0]); + $this->assertCount(35, $result['courses'][1]); // One field more (course format options), not present in site course. $result = core_course_external::get_courses_by_field('id', $course1->id); $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); $this->assertCount(1, $result['courses']); $this->assertEquals($course1->id, $result['courses'][0]['id']); // Expect to receive all the files that a student can see. - $this->assertCount(34, $result['courses'][0]); + $this->assertCount(35, $result['courses'][0]); // Check default filters. $filters = $result['courses'][0]['filters']; @@ -2850,15 +2872,15 @@ class externallib_test extends externallib_advanced_testcase { $result = core_course_external::get_courses_by_field(); $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); $this->assertCount(2, $result['courses']); - $this->assertCount(33, $result['courses'][0]); // Site course. - $this->assertCount(16, $result['courses'][1]); // Only public information, not enrolled. + $this->assertCount(34, $result['courses'][0]); // Site course. + $this->assertCount(17, $result['courses'][1]); // Only public information, not enrolled. $result = core_course_external::get_courses_by_field('id', $course1->id); $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result); $this->assertCount(1, $result['courses']); $this->assertEquals($course1->id, $result['courses'][0]['id']); // Expect to receive all the files that a authenticated can see. - $this->assertCount(16, $result['courses'][0]); + $this->assertCount(17, $result['courses'][0]); // Course 2 is not visible. $result = core_course_external::get_courses_by_field('id', $course2->id); @@ -3882,4 +3904,5 @@ class externallib_test extends externallib_advanced_testcase { $this->assertFalse($favouritefactory->favourite_exists($component, $areaname, $areaid, $context)); $this->assertFalse($result['status']); } + } diff --git a/course/upgrade.txt b/course/upgrade.txt index 9de0270ddc1..e1dd6d8ab4d 100644 --- a/course/upgrade.txt +++ b/course/upgrade.txt @@ -8,6 +8,8 @@ information provided here is intended especially for developers. - $sectionid to specify section the duplicated course module is placed in - $changename to disable changing the name of the course module using the 'duplicatedmodule' lang string * The method `make_categories_options`, deprecated since 3.10, has been removed +* External function core_course_external::get_courses_by_field and core_course_external::search_courses now return a field +called "courseimage" containing a URL pointing to the course image. === 4.1 === * The function course_modchooser() has been finally deprecated and can not be used anymore. Please use diff --git a/enrol/externallib.php b/enrol/externallib.php index e0333a3b8ca..32c46afca3f 100644 --- a/enrol/externallib.php +++ b/enrol/externallib.php @@ -301,7 +301,7 @@ class core_enrol_external extends external_api { * @return array of courses */ public static function get_users_courses($userid, $returnusercount = true) { - global $CFG, $USER, $DB; + global $CFG, $USER, $DB, $OUTPUT; require_once($CFG->dirroot . '/course/lib.php'); require_once($CFG->dirroot . '/user/lib.php'); @@ -411,6 +411,11 @@ class core_enrol_external extends external_api { ); } + $courseimage = \core_course\external\course_summary_exporter::get_course_image($course); + if (!$courseimage) { + $courseimage = $OUTPUT->get_generated_url_for_course($context); + } + $courseresult = [ 'id' => $course->id, 'shortname' => $course->shortname, @@ -421,6 +426,7 @@ class core_enrol_external extends external_api { 'summary' => $course->summary, 'summaryformat' => $course->summaryformat, 'format' => $course->format, + 'courseimage' => $courseimage, 'showgrades' => $course->showgrades, 'lang' => clean_param($course->lang, PARAM_LANG), 'enablecompletion' => $course->enablecompletion, @@ -469,6 +475,7 @@ class core_enrol_external extends external_api { 'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL), 'summaryformat' => new external_format_value('summary', VALUE_OPTIONAL), 'format' => new external_value(PARAM_PLUGIN, 'course format: weeks, topics, social, site', VALUE_OPTIONAL), + 'courseimage' => new external_value(PARAM_URL, 'The course image URL', VALUE_OPTIONAL), 'showgrades' => new external_value(PARAM_BOOL, 'true if grades are shown, otherwise false', VALUE_OPTIONAL), 'lang' => new external_value(PARAM_LANG, 'forced course language', VALUE_OPTIONAL), 'enablecompletion' => new external_value(PARAM_BOOL, 'true if completion is enabled, otherwise false', diff --git a/enrol/tests/externallib_test.php b/enrol/tests/externallib_test.php index b0199c0bafe..e4f0e243d44 100644 --- a/enrol/tests/externallib_test.php +++ b/enrol/tests/externallib_test.php @@ -562,6 +562,8 @@ class externallib_test extends externallib_advanced_testcase { $this->assertTrue($courseenrol['isfavourite']); $this->assertEquals(2, $courseenrol['enrolledusercount']); $this->assertEquals($course1->timemodified, $courseenrol['timemodified']); + $url = "https://www.example.com/moodle/pluginfile.php/{$contexts[$course1->id]->id}/course/generated/course.svg"; + $this->assertEquals($url, $courseenrol['courseimage']); } else { // Check language pack. Should be empty since an incorrect one was used when creating the course. $this->assertEmpty($courseenrol['lang']); @@ -576,6 +578,8 @@ class externallib_test extends externallib_advanced_testcase { $this->assertFalse($courseenrol['isfavourite']); $this->assertEquals(1, $courseenrol['enrolledusercount']); $this->assertEquals($course2->timemodified, $courseenrol['timemodified']); + $url = "https://www.example.com/moodle/pluginfile.php/{$contexts[$course2->id]->id}/course/generated/course.svg"; + $this->assertEquals($url, $courseenrol['courseimage']); } } diff --git a/enrol/upgrade.txt b/enrol/upgrade.txt index 3ac64afd31d..5bf66904d68 100644 --- a/enrol/upgrade.txt +++ b/enrol/upgrade.txt @@ -4,6 +4,8 @@ information provided here is intended especially for developers. === 4.2 === * New is_self_enrol_available() function has been created. Similar to can_self_enrol but without checking user capabilities. +* External function core_enrol_external::get_users_courses now returns a field called "courseimage" containing a URL pointing to + the course image. === 4.0 === diff --git a/lib/classes/geopattern.php b/lib/classes/geopattern.php index 3dc15dcb042..d5190a39cc7 100644 --- a/lib/classes/geopattern.php +++ b/lib/classes/geopattern.php @@ -40,5 +40,4 @@ class core_geopattern extends \RedeyeVentures\GeoPattern\GeoPattern { public function datauri() { return $this->toDataURI(); } - } diff --git a/lib/filelib.php b/lib/filelib.php index 57f170f9d2f..60560e3455e 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -4364,7 +4364,7 @@ class curl_cache { * @todo MDL-31088 file serving improments */ function file_pluginfile($relativepath, $forcedownload, $preview = null, $offline = false, $embed = false) { - global $DB, $CFG, $USER; + global $DB, $CFG, $USER, $OUTPUT; // relative path must start with '/' if (!$relativepath) { throw new \moodle_exception('invalidargorconf'); @@ -4917,6 +4917,18 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null, $offlin \core\session\manager::write_close(); // Unlock session during file serving. send_stored_file($file, 60*60, 0, $forcedownload, $sendfileoptions); + } else if ($filearea === 'generated') { + if ($CFG->forcelogin) { + require_login($course); + } else if ($course->id != SITEID) { + require_login($course); + } + + $svg = $OUTPUT->get_generated_svg_for_id($course->id); + + \core\session\manager::write_close(); // Unlock session during file serving. + send_file($svg, 'course.svg', 60 * 60, 0, true, $forcedownload); + } else { send_file_not_found(); } diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 5b0e8d735b2..c19a8639d65 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -1675,6 +1675,30 @@ class core_renderer extends renderer_base { return $pattern->datauri(); } + /** + * Get the course pattern image URL. + * + * @param context_course $context course context object + * @return string URL of the course pattern image in SVG format + */ + public function get_generated_url_for_course(context_course $context): string { + return moodle_url::make_pluginfile_url($context->id, 'course', 'generated', null, '/', 'course.svg')->out(); + } + + /** + * Get the course pattern in SVG format to show on a course card. + * + * @param int $id id to use when generating the pattern + * @return string SVG file contents + */ + public function get_generated_svg_for_id(int $id): string { + $color = $this->get_generated_color_for_id($id); + $pattern = new \core_geopattern(); + $pattern->setColor($color); + $pattern->patternbyid($id); + return $pattern->toSVG(); + } + /** * Get the course color to show on a course card. * diff --git a/lib/tests/outputrenderers_test.php b/lib/tests/outputrenderers_test.php new file mode 100644 index 00000000000..ba20f63bf41 --- /dev/null +++ b/lib/tests/outputrenderers_test.php @@ -0,0 +1,47 @@ +. + +namespace core; + +/** + * Unit tests for lib/outputrenderers. + * + * @package core + * @category test + * @copyright 2023 Rodrigo Mady + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \core_renderer + */ +class outputrenderers_test extends \advanced_testcase { + /** + * Test generated url from course image. + * + * @covers ::get_generated_url_for_course + */ + public function test_get_generated_url_for_course_image() { + global $OUTPUT; + + $this->resetAfterTest(); + + $course = self::getDataGenerator()->create_course(); + $context = \context_course::instance($course->id, IGNORE_MISSING); + + // Get the image with correct course context. + $courseimage = $OUTPUT->get_generated_url_for_course($context); + $url = "https://www.example.com/moodle/pluginfile.php/{$context->id}/course/generated/course.svg"; + $this->assertEquals($url, $courseimage); + } +}