Skip to content

Commit

Permalink
MDL-66940 badges: Create page to display badges info
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
sarjona committed Mar 4, 2022
1 parent 64b9992 commit 90290e5
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 22 deletions.
4 changes: 3 additions & 1 deletion badges/badge_json.php
Expand Up @@ -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;
Expand Down
57 changes: 57 additions & 0 deletions badges/badgeclass.php
@@ -0,0 +1,57 @@
<?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/>.

/**
* 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();
13 changes: 9 additions & 4 deletions badges/classes/assertion.php
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}

Expand Down
2 changes: 1 addition & 1 deletion badges/classes/badge.php
Expand Up @@ -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) {
Expand Down
175 changes: 175 additions & 0 deletions badges/classes/output/badgeclass.php
@@ -0,0 +1,175 @@
<?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_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;
}
}
11 changes: 11 additions & 0 deletions badges/renderer.php
Expand Up @@ -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.
*
Expand Down
29 changes: 17 additions & 12 deletions badges/templates/issued_badge.mustache
Expand Up @@ -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.
Expand Down Expand Up @@ -139,22 +139,27 @@

<div id="badge-details-col" class="col">
<h2>{{badgename}}</h2>

{{#recipientname}}
<div id="badge-awardedto" class="pt-1 pb-2">
{{#recipientnotification}}
{{> core/notification_warning}}
{{/recipientnotification}}
{{#str}}awardedto, core_badges, {{recipientname}}{{/str}}
</div>
{{/recipientname}}

<div id="badge-issued-expire" class="pt-1 pb-2">
<div class="pb-3">
<small>
{{#str}}
issuedon,
core_badges,
{{#userdate}}{{badgeissuedon}}, {{#str}} strftimedatetime, langconfig {{/str}}{{/userdate}}
{{/str}}
<br/>
{{#badgeissuedon}}
{{#str}}
issuedon,
core_badges,
{{#userdate}}{{badgeissuedon}}, {{#str}} strftimedatetime, langconfig {{/str}}{{/userdate}}
{{/str}}
<br/>
{{/badgeissuedon}}
{{#expiredate}}
{{#str}}
expiresin,
Expand Down
9 changes: 5 additions & 4 deletions lib/classes/event/badge_viewed.php
Expand Up @@ -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']]);
}

/**
Expand All @@ -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.');
}
}

/**
Expand Down

0 comments on commit 90290e5

Please sign in to comment.