From bc7dec8c2bb55dbe01012c1792d6816cc7e02752 Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Mon, 14 Feb 2022 16:33:26 +0100 Subject: [PATCH] MDL-66940 badges: Create page to display badges info The OBv2.0 specification includes a field "Criteria" for BadgeClass. Until now, this field was filled using the URL of the badge assertion, but that is causing some issues in Badgr because it linked to the badge assertion of the first user sending this badge to the Badgr backpack (so then, the following users linked to the first user assertion page too). This patch adds a new page, badgeclass.php which will be used from now to display any badge information which is not related to any assertion (like happens with the criteria in BadgeClass). --- badges/badge_json.php | 4 +- badges/badgeclass.php | 57 ++++++++ badges/classes/assertion.php | 13 +- badges/classes/badge.php | 2 +- badges/classes/output/badgeclass.php | 175 +++++++++++++++++++++++++ badges/renderer.php | 11 ++ badges/templates/issued_badge.mustache | 29 ++-- lib/classes/event/badge_viewed.php | 9 +- 8 files changed, 278 insertions(+), 22 deletions(-) create mode 100644 badges/badgeclass.php create mode 100644 badges/classes/output/badgeclass.php diff --git a/badges/badge_json.php b/badges/badge_json.php index b7526d5ec7625..60f25a589f95d 100644 --- a/badges/badge_json.php +++ b/badges/badge_json.php @@ -66,7 +66,9 @@ $json['image'] = $urlimage; } - $json['criteria']['id'] = $url->out(false); + $params = ['id' => $badge->id]; + $badgeurl = new moodle_url('/badges/badgeclass.php', $params); + $json['criteria']['id'] = $badgeurl->out(false); $json['criteria']['narrative'] = $badge->markdown_badge_criteria(); $json['issuer'] = $badge->get_badge_issuer(); $json['@context'] = OPEN_BADGES_V2_CONTEXT; diff --git a/badges/badgeclass.php b/badges/badgeclass.php new file mode 100644 index 0000000000000..c71b07df896c7 --- /dev/null +++ b/badges/badgeclass.php @@ -0,0 +1,57 @@ +. + +/** + * Display details of a badge. + * + * @package core_badges + * @copyright 2022 Sara Arjona (sara@moodle.com) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(__DIR__ . '/../config.php'); +require_once($CFG->libdir . '/badgeslib.php'); + +$badgeid = required_param('id', PARAM_ALPHANUM); +$badgeclass = new \core_badges\output\badgeclass($badgeid); + +$context = !empty($badgeclass) ? $badgeclass->context : \context_system::instance(); +$PAGE->set_context($context); +$output = $PAGE->get_renderer('core', 'badges'); +$PAGE->set_url('/badges/badgeclass.php', ['id' => $badgeid]); +$PAGE->set_pagelayout('base'); +$PAGE->set_title(get_string('badgedetails', 'badges')); + +if (!empty($badgeclass->badge)) { + $PAGE->navbar->add($badgeclass->badge->name); + $url = new moodle_url($CFG->wwwroot); + navigation_node::override_active_url($url); + + echo $OUTPUT->header(); + echo $output->render($badgeclass); +} else { + echo $OUTPUT->header(); + echo $OUTPUT->notification(get_string('error:relatedbadgedoesntexist', 'badges')); +} + +// Trigger event, badge viewed. +$other = ['badgeid' => $badgeclass->badgeid]; +$eventparams = ['context' => $PAGE->context, 'other' => $other]; + +$event = \core\event\badge_viewed::create($eventparams); +$event->trigger(); + +echo $OUTPUT->footer(); diff --git a/badges/classes/assertion.php b/badges/classes/assertion.php index bbfa50b4f0cf0..6565493c7c3bb 100644 --- a/badges/classes/assertion.php +++ b/badges/classes/assertion.php @@ -196,7 +196,10 @@ public function get_badge_class($issued = true) { } } $class['image'] = 'data:image/png;base64,' . $imagedata; - $class['criteria'] = $this->_url->out(false); // Currently issued badge URL. + + $params = ['id' => $this->get_badge_id()]; + $badgeurl = new moodle_url('/badges/badgeclass.php', $params); + $class['criteria'] = $badgeurl->out(false); // Currently badge URL. if ($issued) { $params = ['id' => $this->get_badge_id(), 'obversion' => $this->_obversion]; $issuerurl = new moodle_url('/badges/issuer_json.php', $params); @@ -281,13 +284,15 @@ public function get_endorsement() { public function get_criteria_badge_class() { $badge = new badge($this->_data->id); $narrative = $badge->markdown_badge_criteria(); + $params = ['id' => $this->get_badge_id()]; + $badgeurl = new moodle_url('/badges/badgeclass.php', $params); if (!empty($narrative)) { - $criteria = array(); - $criteria['id'] = $this->_url->out(false); + $criteria = []; + $criteria['id'] = $badgeurl->out(false); $criteria['narrative'] = $narrative; return $criteria; } else { - return $this->_url->out(false); + return $badgeurl->out(false); } } diff --git a/badges/classes/badge.php b/badges/classes/badge.php index 6bae4e781cd0d..a518607029160 100644 --- a/badges/classes/badge.php +++ b/badges/classes/badge.php @@ -141,7 +141,7 @@ public function __construct($badgeid) { $data = $DB->get_record('badge', array('id' => $badgeid)); if (empty($data)) { - print_error('error:nosuchbadge', 'badges', $badgeid); + throw new moodle_exception('error:nosuchbadge', 'badges', '', $badgeid); } foreach ((array)$data as $field => $value) { diff --git a/badges/classes/output/badgeclass.php b/badges/classes/output/badgeclass.php new file mode 100644 index 0000000000000..d66b37fd0c01f --- /dev/null +++ b/badges/classes/output/badgeclass.php @@ -0,0 +1,175 @@ +. + +namespace core_badges\output; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/badgeslib.php'); + +use coding_exception; +use context_course; +use stdClass; +use renderable; +use core_badges\badge; +use moodle_url; +use renderer_base; + +/** + * Page to display badge information, such as name, description or criteria. This information is unrelated to assertions. + * + * @package core_badges + * @copyright 2022 Sara Arjona (sara@moodle.com) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class badgeclass implements renderable { + + /** @var badge class */ + public $badge; + + /** @var badge class */ + public $badgeid = 0; + + /** @var \context The badge context*/ + public $context; + + /** + * Initializes the badge to display. + * + * @param int $id Id of the badge to display. + */ + public function __construct(int $id) { + $this->badgeid = $id; + $this->badge = new badge($this->badgeid); + if ($this->badge->status == BADGE_STATUS_INACTIVE) { + // Inactive badges that haven't been published previously can't be displayed. + $this->badge = null; + } else { + $this->context = $this->badge->get_context(); + } + } + + /** + * Export this data so it can be used as the context for a mustache template. + * + * @param renderer_base $output Renderer base. + * @return stdClass + */ + public function export_for_template(renderer_base $output): stdClass { + global $DB, $SITE; + + $data = new stdClass(); + if ($this->context instanceof context_course) { + $data->coursefullname = format_string($DB->get_field('course', 'fullname', ['id' => $this->badge->courseid]), + true, ['context' => $this->context]); + } else { + $data->sitefullname = format_string($SITE->fullname, true, ['context' => $this->context]); + } + + // Field: Image. + $storage = get_file_storage(); + $imagefile = $storage->get_file($this->context->id, 'badges', 'badgeimage', $this->badgeid, '/', 'f3.png'); + if ($imagefile) { + $imagedata = base64_encode($imagefile->get_content()); + } else { + if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) { + // Unit tests the file might not exist yet. + $imagedata = ''; + } else { + throw new coding_exception('Image file does not exist.'); + } + } + $data->badgeimage = 'data:image/png;base64,' . $imagedata; + + // Fields: Name, description. + $data->badgename = $this->badge->name; + $data->badgedescription = $this->badge->description; + + // Field: Criteria. + // This method will return the HTML with the badge criteria. + $data->criteria = $output->print_badge_criteria($this->badge); + + // Field: Issuer. + $data->issuedby = format_string($this->badge->issuername, true, ['context' => $this->context]); + if (isset($this->badge->issuercontact) && !empty($this->badge->issuercontact)) { + $data->issuedbyemailobfuscated = obfuscate_mailto($this->badge->issuercontact, $data->issuedby); + } + + // Fields: Other details, such as language or version. + $data->hasotherfields = false; + if (!empty($this->badge->language)) { + $data->hasotherfields = true; + $languages = get_string_manager()->get_list_of_languages(); + $data->language = $languages[$this->badge->language]; + } + if (!empty($this->badge->version)) { + $data->hasotherfields = true; + $data->version = $this->badge->version; + } + if (!empty($this->badge->imageauthorname)) { + $data->hasotherfields = true; + $data->imageauthorname = $this->badge->imageauthorname; + } + if (!empty($this->badge->imageauthoremail)) { + $data->hasotherfields = true; + $data->imageauthoremail = obfuscate_mailto($this->badge->imageauthoremail, $this->badge->imageauthoremail); + } + if (!empty($this->badge->imageauthorurl)) { + $data->hasotherfields = true; + $data->imageauthorurl = $this->badge->imageauthorurl; + } + if (!empty($this->badge->imagecaption)) { + $data->hasotherfields = true; + $data->imagecaption = $this->badge->imagecaption; + } + + // Field: Endorsement. + $endorsement = $this->badge->get_endorsement(); + if (!empty($endorsement)) { + $data->hasotherfields = true; + $endorsement = $this->badge->get_endorsement(); + $endorsement->issueremail = obfuscate_mailto($endorsement->issueremail, $endorsement->issueremail); + $data->endorsement = (array) $endorsement; + } + + // Field: Related badges. + $relatedbadges = $this->badge->get_related_badges(true); + if (!empty($relatedbadges)) { + $data->hasotherfields = true; + $data->hasrelatedbadges = true; + $data->relatedbadges = []; + foreach ($relatedbadges as $related) { + if (isloggedin() && !is_guest($this->context)) { + $related->url = (new moodle_url('/badges/overview.php', ['id' => $related->id]))->out(false); + } + $data->relatedbadges[] = (array)$related; + } + } + + // Field: Alignments. + $alignments = $this->badge->get_alignments(); + if (!empty($alignments)) { + $data->hasotherfields = true; + $data->hasalignments = true; + $data->alignments = []; + foreach ($alignments as $alignment) { + $data->alignments[] = (array)$alignment; + } + } + + return $data; + } +} diff --git a/badges/renderer.php b/badges/renderer.php index 8b789061356a3..57cc70a7312e3 100644 --- a/badges/renderer.php +++ b/badges/renderer.php @@ -327,6 +327,17 @@ protected function render_issued_badge(\core_badges\output\issued_badge $ibadge) return parent::render_from_template('core_badges/issued_badge', $data); } + /** + * Render an issued badge. + * + * @param \core_badges\output\badgeclass $badge + * @return string + */ + protected function render_badgeclass(\core_badges\output\badgeclass $badge) { + $data = $badge->export_for_template($this); + return parent::render_from_template('core_badges/issued_badge', $data); + } + /** * Render an external badge. * diff --git a/badges/templates/issued_badge.mustache b/badges/templates/issued_badge.mustache index 6d57d37814c3d..57077ab49c2ec 100644 --- a/badges/templates/issued_badge.mustache +++ b/badges/templates/issued_badge.mustache @@ -23,14 +23,14 @@ * coursefullname - Course name (only available if it's a course badge). * sitefullname - Site name (only available if it's a site badge). * badgeimage - Badge image. - * expireddate - Date (in the past) when the badge expired (if defined). If expiredate is defined, this field will be empty. - * expireddateformatted - Formatted expired date. - * expiredate - Date (in the future) when the badge will expire (if defined). If expireddate is defined, this field will be empty. + * expireddate - Date (in the past) when the badge expired. If expiredate is defined, this field will be empty [optional]. + * expireddateformatted - Formatted expired date [optional]. + * expiredate - Date (in the future) when the badge will expire. If expireddate is defined, this field will be empty [optional]. * badgename - Badge name. * badgedescription - Badge description. - * badgeissuedon - Date where the badge was issued on by the user. - * recipientname - User awarded with the badge. - * recipientnotification.message - Message to be displayed if there is some issue with the recipient. + * badgeissuedon - Date where the badge was issued on by the user [optional]. + * recipientname - User awarded with the badge [optional]. + * recipientnotification.message - Message to be displayed if there is some issue with the recipient [optional]. * criteria - HTML code with the criteria to display. * issuedby - Badge issuer. * issuedbyemailobfuscated - Badge issuer email link obfuscated. @@ -139,22 +139,27 @@

{{badgename}}

+ + {{#recipientname}}
{{#recipientnotification}} {{> core/notification_warning}} {{/recipientnotification}} {{#str}}awardedto, core_badges, {{recipientname}}{{/str}}
+ {{/recipientname}}
- {{#str}} - issuedon, - core_badges, - {{#userdate}}{{badgeissuedon}}, {{#str}} strftimedatetime, langconfig {{/str}}{{/userdate}} - {{/str}} -
+ {{#badgeissuedon}} + {{#str}} + issuedon, + core_badges, + {{#userdate}}{{badgeissuedon}}, {{#str}} strftimedatetime, langconfig {{/str}}{{/userdate}} + {{/str}} +
+ {{/badgeissuedon}} {{#expiredate}} {{#str}} expiresin, diff --git a/lib/classes/event/badge_viewed.php b/lib/classes/event/badge_viewed.php index 1f1e15a3bdf59..5beda67e2fcb1 100644 --- a/lib/classes/event/badge_viewed.php +++ b/lib/classes/event/badge_viewed.php @@ -73,7 +73,11 @@ public function get_description() { * @return \moodle_url */ public function get_url() { - return new \moodle_url('/badges/badge.php', array('hash' => $this->other['badgehash'])); + if (isset($this->other['badgehash'])) { + return new \moodle_url('/badges/badge.php', ['hash' => $this->other['badgehash']]); + } + + return new \moodle_url('/badges/badgeclass.php', ['id' => $this->other['badgeid']]); } /** @@ -88,9 +92,6 @@ protected function validate_data() { if (!isset($this->other['badgeid'])) { throw new \coding_exception('The \'badgeid\' must be set in other.'); } - if (!isset($this->other['badgehash'])) { - throw new \coding_exception('The \'badgehash\' must be set in other.'); - } } /**