From 33717237aa75b7052a3f53a920ea508bfdb950aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ure=C3=B1a?= Date: Mon, 17 Jun 2024 02:10:01 +0200 Subject: [PATCH] MDL-82105 Badges: Get badge by badge id. --- badges/classes/external/get_badge.php | 111 ++++++++++++++++ badges/tests/external/get_badge_test.php | 155 +++++++++++++++++++++++ lib/badgeslib.php | 75 +++++++++++ lib/db/services.php | 7 + version.php | 2 +- 5 files changed, 349 insertions(+), 1 deletion(-) create mode 100644 badges/classes/external/get_badge.php create mode 100644 badges/tests/external/get_badge_test.php diff --git a/badges/classes/external/get_badge.php b/badges/classes/external/get_badge.php new file mode 100644 index 00000000000..ac0791cfa80 --- /dev/null +++ b/badges/classes/external/get_badge.php @@ -0,0 +1,111 @@ +. + +namespace core_badges\external; + +use core_external\external_api; +use core_external\external_function_parameters; +use core_external\external_single_structure; +use core_external\external_value; +use core_external\external_warnings; +use moodle_exception; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/badgeslib.php'); + +/** + * External service to get badge by id. + * + * This is mainly used by the mobile application. + * + * @package core_badges + * @category external + * + * @copyright 2024 Daniel Ureña + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 4.5 + */ +class get_badge extends external_api { + /** + * Returns description of method parameters + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'id' => new external_value(PARAM_INT, 'Badge id', VALUE_REQUIRED), + ]); + } + + /** + * Execute the get badge by id. + * + * @param int $id + * @return array + * @throws \restricted_context_exception + */ + public static function execute(int $id): array { + global $CFG; + + // Initialize return variables. + $warnings = []; + + // Validate the id. + [ + 'id' => $id, + ] = self::validate_parameters(self::execute_parameters(), [ + 'id' => $id, + ]); + // Validate badges is not disabled. + if (empty($CFG->enablebadges)) { + throw new moodle_exception('badgesdisabled', 'badges'); + } + + $context = \context_system::instance(); + if (!has_capability('moodle/badges:viewbadges', $context)) { + throw new moodle_exception('error:nopermissiontoview', 'badges'); + } + + // Get the badge by id. + $badgeclass = new \core_badges\output\badgeclass($id); + + if (empty($badgeclass->badge)) { + throw new moodle_exception('error:relatedbadgedoesntexist', 'badges'); + } + + // Manage the badge. + $result = badges_prepare_badgeclass_for_external($badgeclass); + + return [ + 'badge' => $result, + 'warnings' => $warnings, + ]; + } + + /** + * Describe the return structure of the external service. + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return new external_single_structure([ + 'badge' => badgeclass_exporter::get_read_structure(), + 'warnings' => new external_warnings(), + ]); + } +} diff --git a/badges/tests/external/get_badge_test.php b/badges/tests/external/get_badge_test.php new file mode 100644 index 00000000000..5820b34fa46 --- /dev/null +++ b/badges/tests/external/get_badge_test.php @@ -0,0 +1,155 @@ +. + +namespace core_badges\external; + +use externallib_advanced_testcase; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; + +require_once($CFG->dirroot . '/webservice/tests/helpers.php'); +require_once($CFG->libdir . '/badgeslib.php'); + +/** + * Tests for external function get_badge. + * + * @package core_badges + * @category external + * + * @copyright 2024 Daniel Ureña + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 4.5 + * @coversDefaultClass \core_badges\external\get_badge + */ +final class get_badge_test extends externallib_advanced_testcase { + /** + * Prepare the test. + * + * @return array + */ + private function prepare_test_data(): array { + global $DB; + $this->resetAfterTest(); + $this->setAdminUser(); + set_config('enablebadges', 1); + + // Mock up a site badge. + $now = time(); + $badge = new \stdClass(); + $badge->id = null; + $badge->name = "Test badge site"; + $badge->description = "Testing badges site"; + $badge->timecreated = $now; + $badge->timemodified = $now; + $badge->usercreated = 2; + $badge->usermodified = 2; + $badge->expiredate = null; + $badge->expireperiod = null; + $badge->type = BADGE_TYPE_SITE; + $badge->courseid = null; + $badge->messagesubject = "Test message subject for badge"; + $badge->message = "Test message body for badge"; + $badge->attachment = 1; + $badge->notification = 0; + $badge->status = BADGE_STATUS_ACTIVE; + $badge->version = '1'; + $badge->language = 'en'; + $badge->imageauthorname = 'Image author'; + $badge->imageauthoremail = 'imageauthor@example.com'; + $badge->imageauthorurl = 'http://image-author-url.domain.co.nz'; + $badge->imagecaption = 'Caption'; + + $badgeid = $DB->insert_record('badge', $badge, true); + $badge->id = $badgeid; + + $context = \context_system::instance(); + $badge->badgeurl = \moodle_url::make_webservice_pluginfile_url( + $context->id, + 'badges', + 'badgeimage', + $badge->id, + '/', + 'f3' + )->out(false); + $badge->status = BADGE_STATUS_ACTIVE_LOCKED; + + return ['badge' => (array) $badge]; + } + + /** + * Test get badge by id without enablebadges active in moodle. + * @covers ::execute + */ + public function test_get_badge_without_enablebadges(): void { + $data = $this->prepare_test_data(); + // Badges are not enabled on this site. + set_config('enablebadges', 0); + + $this->expectException(\moodle_exception::class); + $this->expectExceptionMessage('Badges are not enabled on this site.'); + get_badge::execute($data['badge']['id']); + } + + /** + * Check the capability to view badges. + * @covers ::execute + */ + public function test_get_badge_viewbadges(): void { + + global $DB, $SITE; + $data = $this->prepare_test_data(); + + $capability = 'moodle/badges:viewbadges'; + $role = $DB->get_record('role', ['shortname' => 'student']); + $context = \context_system::instance(); + assign_capability($capability, CAP_PROHIBIT, $role->id, $context->id, true); + + $student1 = $this->getDataGenerator()->create_and_enrol($SITE, 'student'); + role_assign($role->id, $student1->id, $context->id); + accesslib_clear_all_caches_for_unit_testing(); + + $this->setUser($student1); + $this->expectException(\moodle_exception::class); + get_badge::execute($data['badge']['id']); + } + + /** + * Test get badge by id. + * @covers ::execute + */ + public function test_get_badge(): void { + $data = $this->prepare_test_data(); + + // Test with an existing badge. + $result = get_badge::execute($data['badge']['id']); + $result = \core_external\external_api::clean_returnvalue(get_badge::execute_returns(), $result); + $this->assertEquals($data['badge']['name'], $result['badge']['name']); + $this->assertEmpty($result['warnings']); + } + + /** + * Test get badge by id with an invalid badge id. + * @covers ::execute + */ + public function test_get_badge_with_invalid_badge_id(): void { + $data = $this->prepare_test_data(); + + $this->expectException(\moodle_exception::class); + get_badge::execute(123); + } +} diff --git a/lib/badgeslib.php b/lib/badgeslib.php index 1802ed78b18..510e118e160 100644 --- a/lib/badgeslib.php +++ b/lib/badgeslib.php @@ -31,6 +31,8 @@ require_once($CFG->dirroot . '/badges/criteria/award_criteria.php'); /* Include required user badge exporter */ use core_badges\external\user_badge_exporter; +/* Include required badge class exporter */ +use core_badges\external\badgeclass_exporter; /* * Number of records per page. @@ -497,6 +499,79 @@ function badges_prepare_badge_for_external(stdClass $badge, stdClass $user): obj return $exporter->export($PAGE->get_renderer('core')); } +/** + * Prepare badgeclass for external functions. + * @param core_badges\output\badgeclass $badgeclass + * @return stdClass + */ +function badges_prepare_badgeclass_for_external(core_badges\output\badgeclass $badgeclass): stdClass { + global $PAGE; + $context = $badgeclass->context; + + $badgeurl = new \moodle_url('/badges/badgeclass.php', [ + 'id' => $badgeclass->badge->id, + ]); + $badgeurl = $badgeurl->out(false); + $file = \moodle_url::make_webservice_pluginfile_url( + $badgeclass->context->id, + 'badges', + 'badgeimage', + $badgeclass->badge->id, + '/', + 'f3' + ); + $image = $file->out(false); + + $badge = (object) [ + 'id' => $badgeurl, + 'name' => $badgeclass->badge->name, + 'type' => OPEN_BADGES_V2_TYPE_BADGE, + 'description' => $badgeclass->badge->description, + 'issuer' => $badgeclass->badge->issuername, + 'hostedUrl' => $badgeclass->badge->issuerurl, + 'image' => $image, + ]; + // Create a badge instance to be able to get the endorsement and other info. + $badgeinstance = new badge($badgeclass->badge->id); + $endorsement = $badgeinstance->get_endorsement(); + $alignments = $badgeinstance->get_alignments(); + $relatedbadges = $badgeinstance->get_related_badges(); + + $canconfiguredetails = has_capability('moodle/badges:configuredetails', $context); + + if (!$canconfiguredetails) { + // Return only the properties visible by the user. + if (!empty($alignments)) { + foreach ($alignments as $alignment) { + unset($alignment->targetdescription); + unset($alignment->targetframework); + unset($alignment->targetcode); + } + } + + if (!empty($relatedbadges)) { + foreach ($relatedbadges as $relatedbadge) { + unset($relatedbadge->version); + unset($relatedbadge->language); + unset($relatedbadge->type); + } + } + } + + $related = [ + 'context' => $context, + 'endorsement' => $endorsement ? $endorsement : null, + 'relatedbadges' => $relatedbadges, + ]; + + if (!empty($alignments)) { + $related['alignment'] = $alignments; + } + + $exporter = new badgeclass_exporter($badge, $related); + return $exporter->export($PAGE->get_renderer('core', 'badges')); +} + /** * Extends the course administration navigation with the Badges page * diff --git a/lib/db/services.php b/lib/db/services.php index fcf55a7d3b4..2adb1ff5ef5 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -131,6 +131,13 @@ $functions = array( 'type' => 'write', 'ajax' => true, ], + 'core_badges_get_badge' => [ + 'classname' => 'core_badges\external\get_badge', + 'description' => 'Retrieves a badge by id.', + 'type' => 'read', + 'capabilities' => 'moodle/badges:viewbadges', + 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE], + ], 'core_badges_get_user_badges' => array( 'classname' => 'core_badges_external', 'methodname' => 'get_user_badges', diff --git a/version.php b/version.php index 3784af73196..be8fc5ae855 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2024082300.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2024082300.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.5dev+ (Build: 20240823)'; // Human-friendly version name