Skip to content

Commit

Permalink
MDL-73325 core_badges: Add tags field to BadgeClass
Browse files Browse the repository at this point in the history
  • Loading branch information
djhipps committed Jun 9, 2023
1 parent 71c36d2 commit faa41cd
Show file tree
Hide file tree
Showing 17 changed files with 205 additions and 28 deletions.
11 changes: 11 additions & 0 deletions backup/moodle2/backup_stepslib.php
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,9 @@ protected function define_structure() {
$manual_award = new backup_nested_element('manual_award', array('id'), array('badgeid',
'recipientid', 'issuerid', 'issuerrole', 'datemet'));

$tags = new backup_nested_element('tags');
$tag = new backup_nested_element('tag', ['id'], ['name', 'rawname']);

// Build the tree.

$badges->add_child($badge);
Expand All @@ -953,6 +956,8 @@ protected function define_structure() {
$relatedbadges->add_child($relatedbadge);
$badge->add_child($manual_awards);
$manual_awards->add_child($manual_award);
$badge->add_child($tags);
$tags->add_child($tag);

// Define sources.

Expand Down Expand Up @@ -980,6 +985,12 @@ protected function define_structure() {

$manual_award->set_source_table('badge_manual_award', array('badgeid' => backup::VAR_PARENTID));

$tag->set_source_sql('SELECT t.id, t.name, t.rawname
FROM {tag} t
JOIN {tag_instance} ti ON ti.tagid = t.id
WHERE ti.itemtype = ?
AND ti.itemid = ?', [backup_helper::is_sqlparam('badge'), backup::VAR_PARENTID]);

// Define id annotations.

$badge->annotate_ids('user', 'usercreated');
Expand Down
17 changes: 17 additions & 0 deletions backup/moodle2/restore_stepslib.php
Original file line number Diff line number Diff line change
Expand Up @@ -2621,6 +2621,7 @@ protected function define_structure() {
$paths[] = new restore_path_element('alignment', '/badges/badge/alignments/alignment');
$paths[] = new restore_path_element('relatedbadge', '/badges/badge/relatedbadges/relatedbadge');
$paths[] = new restore_path_element('manual_award', '/badges/badge/manual_awards/manual_award');
$paths[] = new restore_path_element('tag', '/badges/badge/tags/tag');

return $paths;
}
Expand Down Expand Up @@ -2830,6 +2831,22 @@ public function process_manual_award($data) {
}
}

/**
* Process tag.
*
* @param array $data The data.
* @throws base_step_exception
*/
public function process_tag(array $data): void {
$data = (object)$data;
$badgeid = $this->get_new_parentid('badge');

if (!empty($data->rawname)) {
core_tag_tag::add_item_tag('core_badges', 'badge', $badgeid,
context_course::instance($this->get_courseid()), $data->rawname);
}
}

protected function after_execute() {
global $DB;
// Add related files.
Expand Down
4 changes: 4 additions & 0 deletions badges/badge_json.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@
if (!empty($badge->language)) {
$json['@language'] = $badge->language;
}
$badgetags = $badge->get_badge_tags();
if ($badgetags) {
$json['tags'] = $badgetags;
}

$relatedbadges = $badge->get_related_badges(true);
if (!empty($relatedbadges)) {
Expand Down
17 changes: 17 additions & 0 deletions badges/classes/assertion.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ public function get_badge_assertion($issued = true, $usesalt = true) {
if (!empty($this->_data->dateexpire)) {
$assertion['expires'] = $this->_data->dateexpire;
}
$tags = $this->get_tags();
if (is_array($tags) && count($tags) > 0) {
$assertion['tags'] = $tags;
}
$this->embed_data_badge_version2($assertion, OPEN_BADGES_V2_TYPE_ASSERTION);
}
return $assertion;
Expand Down Expand Up @@ -205,6 +209,10 @@ public function get_badge_class($issued = true) {
$issuerurl = new moodle_url('/badges/issuer_json.php', $params);
$class['issuer'] = $issuerurl->out(false);
}
$tags = $this->get_tags();
if (is_array($tags) && count($tags) > 0) {
$class['tags'] = $tags;
}
$this->embed_data_badge_version2($class, OPEN_BADGES_V2_TYPE_BADGE);
if (!$issued) {
unset($class['issuer']);
Expand Down Expand Up @@ -404,4 +412,13 @@ protected function embed_data_badge_version2 (&$json, $type = OPEN_BADGES_V2_TYP
}
}
}

/**
* Get tags of the badge.
*
* @return array tags.
*/
public function get_tags(): array {
return array_values(\core_tag_tag::get_item_tags_array('core_badges', 'badge', $this->get_badge_id()));
}
}
15 changes: 15 additions & 0 deletions badges/classes/badge.php
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ public function make_clone() {
$fordb->usermodified = $USER->id;
$fordb->timecreated = time();
$fordb->timemodified = time();
$tags = $this->get_badge_tags();
unset($fordb->id);

if ($fordb->notification > 1) {
Expand All @@ -313,6 +314,8 @@ public function make_clone() {

if ($new = $DB->insert_record('badge', $fordb, true)) {
$newbadge = new badge($new);
// Copy badge tags.
\core_tag_tag::set_item_tags('core_badges', 'badge', $newbadge->id, $this->get_context(), $tags);

// Copy badge image.
$fs = get_file_storage();
Expand Down Expand Up @@ -745,6 +748,9 @@ public function delete($archive = true) {
$DB->delete_records_select('badge_related', $relatedsql, $relatedparams);
$DB->delete_records('badge_alignment', array('badgeid' => $this->id));

// Delete all tags.
\core_tag_tag::remove_all_item_tags('core_badges', 'badge', $this->id);

// Finally, remove badge itself.
$DB->delete_records('badge', array('id' => $this->id));

Expand Down Expand Up @@ -976,4 +982,13 @@ public function get_badge_issuer(?int $obversion = null) {

return $issuer;
}

/**
* Get tags of badge.
*
* @return array Badge tags.
*/
public function get_badge_tags(): array {
return array_values(\core_tag_tag::get_item_tags_array('core_badges', 'badge', $this->id));
}
}
19 changes: 10 additions & 9 deletions badges/classes/form/badge.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public function definition() {
$mform->addElement('text', 'imagecaption', get_string('imagecaption', 'badges'), array('size' => '70'));
$mform->setType('imagecaption', PARAM_TEXT);
$mform->addHelpButton('imagecaption', 'imagecaption', 'badges');

$mform->addElement('tags', 'tags', get_string('tags', 'badges'), ['itemtype' => 'badge', 'component' => 'core_badges']);

if (badges_open_badges_backpack_api() == OPEN_BADGES_V1) {
$mform->addElement('header', 'issuerdetails', get_string('issuerdetails', 'badges'));
Expand Down Expand Up @@ -173,22 +173,23 @@ public function definition() {
/**
* Load in existing data as form defaults
*
* @param stdClass|array $default_values object or array of default values
* @param stdClass|array $badge object or array of default values
*/
public function set_data($badge) {
$default_values = array();
$defaultvalues = [];
parent::set_data($badge);

if (!empty($badge->expiredate)) {
$default_values['expiry'] = 1;
$default_values['expiredate'] = $badge->expiredate;
$defaultvalues['expiry'] = 1;
$defaultvalues['expiredate'] = $badge->expiredate;
} else if (!empty($badge->expireperiod)) {
$default_values['expiry'] = 2;
$default_values['expireperiod'] = $badge->expireperiod;
$defaultvalues['expiry'] = 2;
$defaultvalues['expireperiod'] = $badge->expireperiod;
}
$default_values['currentimage'] = print_badge_image($badge, $badge->get_context(), 'large');
$defaultvalues['tags'] = \core_tag_tag::get_item_tags_array('core_badges', 'badge', $badge->id);
$defaultvalues['currentimage'] = print_badge_image($badge, $badge->get_context(), 'large');

parent::set_data($default_values);
parent::set_data($defaultvalues);
}

/**
Expand Down
5 changes: 5 additions & 0 deletions badges/classes/output/badgeclass.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ public function export_for_template(renderer_base $output): stdClass {
}
}

// Field: Tags.
$tags = \core_tag_tag::get_item_tags('core_badges', 'badge', $this->badgeid);
$taglist = new \core_tag\output\taglist($tags);
$data->badgetag = $taglist->export_for_template($output);

return $data;
}
}
5 changes: 5 additions & 0 deletions badges/classes/output/issued_badge.php
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ public function export_for_template(renderer_base $output): stdClass {
}
}

// Field: Tags.
$tags = \core_tag_tag::get_item_tags('core_badges', 'badge', $this->badgeid);
$taglist = new \core_tag\output\taglist($tags);
$data->badgetag = $taglist->export_for_template($output);

return $data;
}
}
2 changes: 2 additions & 0 deletions badges/edit.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@
unset($badge->message_editor);

if ($badge->save()) {
core_tag_tag::set_item_tags('core_badges', 'badge', $badge->id, $context, $data->tags);
$badge->tags = core_tag_tag::get_item_tags_array('core_badges', 'badge', $badge->id);
badges_process_badge_image($badge, $form->save_temp_file('image'));
$form->set_data($badge);
$statusmsg = get_string('changessaved');
Expand Down
41 changes: 41 additions & 0 deletions badges/lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,44 @@ function core_badges_myprofile_navigation(\core_user\output\myprofile\tree $tree
}
}
}

/**
* Returns badges tagged with a specified tag.
*
* @param object $tag
* @param bool $exclusivemode if set to true it means that no other entities tagged with this tag
* are displayed on the page and the per-page limit may be bigger
* @param null|int $fromctx context id where the link was displayed, may be used by callbacks
* to display items in the same context first
* @param null|int $ctx context id where to search for records
* @param bool $rec search in subcontexts as well
* @param int $page 0-based number of page being displayed
* @return \core_tag\output\tagindex
*/
function badge_get_tagged_badges(object $tag, bool $exclusivemode = false, null|int $fromctx = 0, null|int $ctx = 0,
bool $rec = true, int $page = 0): object {
global $OUTPUT;

$badgecount = $tag->count_tagged_items('core_badges', 'badge');
$perpage = $exclusivemode ? 20 : 5;
$content = '';
$totalpages = ceil($badgecount / $perpage);

if ($badgecount) {
$badges = $tag->get_tagged_items('core_badges', 'badge', $page * $perpage, $perpage);
$tagfeed = new core_tag\output\tagfeed();
foreach ($badges as $badge) {
$badgelink = new moodle_url('/badges/badgeclass.php', ['id' => $badge->id]);
$fullname = html_writer::link($badgelink, $badge->name);
$icon = html_writer::link($badgelink, html_writer::empty_tag('img',
['src' => $OUTPUT->image_url('i/badge')]));
$tagfeed->add($icon, $fullname, '<br>');
}

$items = $tagfeed->export_for_template($OUTPUT);

$content .= $OUTPUT->render_from_template('core_tag/tagfeed', $items);
}
return new core_tag\output\tagindex($tag, 'core_badges', 'badge', $content,
$exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages);
}
1 change: 1 addition & 0 deletions badges/newbadge.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
$event->trigger();

$newbadge = new badge($newid);
core_tag_tag::set_item_tags('core_badges', 'badge', $newid, $PAGE->context, $data->tags);
badges_process_badge_image($newbadge, $form->save_temp_file('image'));
// If a user can configure badge criteria, they will be redirected to the criteria page.
if (has_capability('moodle/badges:configurecriteria', $PAGE->context)) {
Expand Down
2 changes: 2 additions & 0 deletions badges/renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ public function print_badge_overview($badge, $context) {
$dl[get_string('imageauthorurl', 'badges')] =
html_writer::link($badge->imageauthorurl, $badge->imageauthorurl, array('target' => '_blank'));
$dl[get_string('imagecaption', 'badges')] = $badge->imagecaption;
$tags = \core_tag_tag::get_item_tags('core_badges', 'badge', $badge->id);
$dl[get_string('tags', 'badges')] = $this->output->tag_list($tags, '');
$display .= $this->definition_list($dl);

// Issuer details.
Expand Down
5 changes: 5 additions & 0 deletions badges/templates/issued_badge.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@
</div>
{{/coursefullname}}
</div>
{{#badgetag}}
<p class="pb-2">
{{> core_tag/taglist}}
</p>
{{/badgetag}}

<p class="pb-4">{{{badgedescription}}}</p>

Expand Down
42 changes: 37 additions & 5 deletions badges/tests/badgeslib_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,26 +135,30 @@ protected function setUp(): void {
$alignment->targetcode = 'CCSS.RST.11-12.3';
$DB->insert_record('badge_alignment', $alignment, true);

// Insert tags.
core_tag_tag::set_item_tags('core_badges', 'badge', $badge->id, $badge->get_context(), ['tag1', 'tag2']);

$this->assertion = new stdClass();
$this->assertion->badge = '{"uid":"%s","recipient":{"identity":"%s","type":"email","hashed":true,"salt":"%s"},"badge":"%s","verify":{"type":"hosted","url":"%s"},"issuedOn":"%d","evidence":"%s"}';
$this->assertion->class = '{"name":"%s","description":"%s","image":"%s","criteria":"%s","issuer":"%s"}';
$this->assertion->badge = '{"uid":"%s","recipient":{"identity":"%s","type":"email","hashed":true,"salt":"%s"},' .
'"badge":"%s","verify":{"type":"hosted","url":"%s"},"issuedOn":"%d","evidence":"%s","tags":%s}';
$this->assertion->class = '{"name":"%s","description":"%s","image":"%s","criteria":"%s","issuer":"%s","tags":%s}';
$this->assertion->issuer = '{"name":"%s","url":"%s","email":"%s"}';
// Format JSON-LD for Openbadge specification version 2.0.
$this->assertion2 = new stdClass();
$this->assertion2->badge = '{"recipient":{"identity":"%s","type":"email","hashed":true,"salt":"%s"},' .
'"badge":{"name":"%s","description":"%s","image":"%s",' .
'"criteria":{"id":"%s","narrative":"%s"},"issuer":{"name":"%s","url":"%s","email":"%s",' .
'"@context":"https:\/\/w3id.org\/openbadges\/v2","id":"%s","type":"Issuer"},' .
'"@context":"https:\/\/w3id.org\/openbadges\/v2","id":"%s","type":"BadgeClass","version":"%s",' .
'"tags":%s,"@context":"https:\/\/w3id.org\/openbadges\/v2","id":"%s","type":"BadgeClass","version":"%s",' .
'"@language":"en","related":[{"id":"%s","version":"%s","@language":"%s"}],"endorsement":"%s",' .
'"alignments":[{"targetName":"%s","targetUrl":"%s","targetDescription":"%s","targetFramework":"%s",' .
'"targetCode":"%s"}]},"verify":{"type":"hosted","url":"%s"},"issuedOn":"%s","evidence":"%s",' .
'"targetCode":"%s"}]},"verify":{"type":"hosted","url":"%s"},"issuedOn":"%s","evidence":"%s","tags":%s,' .
'"@context":"https:\/\/w3id.org\/openbadges\/v2","type":"Assertion","id":"%s"}';

$this->assertion2->class = '{"name":"%s","description":"%s","image":"%s",' .
'"criteria":{"id":"%s","narrative":"%s"},"issuer":{"name":"%s","url":"%s","email":"%s",' .
'"@context":"https:\/\/w3id.org\/openbadges\/v2","id":"%s","type":"Issuer"},' .
'"@context":"https:\/\/w3id.org\/openbadges\/v2","id":"%s","type":"BadgeClass","version":"%s",' .
'"tags":%s,"@context":"https:\/\/w3id.org\/openbadges\/v2","id":"%s","type":"BadgeClass","version":"%s",' .
'"@language":"%s","related":[{"id":"%s","version":"%s","@language":"%s"}],"endorsement":"%s",' .
'"alignments":[{"targetName":"%s","targetUrl":"%s","targetDescription":"%s","targetFramework":"%s",' .
'"targetCode":"%s"}]}';
Expand Down Expand Up @@ -217,6 +221,8 @@ public function test_delete_badge_for_real() {
global $DB;

$badge = new badge($this->badgeid);
// Insert tags for the badge.
core_tag_tag::set_item_tags('core_badges', 'badge', $badge->id, $badge->get_context(), ['tag1', 'tag2']);

$newid1 = $badge->make_clone();
$newid2 = $badge->make_clone();
Expand All @@ -239,6 +245,9 @@ public function test_delete_badge_for_real() {
// Badge 1 has 4 related records. 3 where it's the badgeid, 1 where it's the relatedbadgeid.
$this->assertEquals(4, $DB->count_records_select('badge_related', $relatedsql, $relatedparams));

// Badge has 2 tag instance records.
$this->assertEquals(2, $DB->count_records('tag_instance', ['itemid' => $this->badgeid]));

// Delete the badge for real.
$badge->delete(false);

Expand All @@ -247,6 +256,9 @@ public function test_delete_badge_for_real() {

// Confirm that the records about this badge about its relations have been removed as well.
$this->assertFalse($DB->record_exists_select('badge_related', $relatedsql, $relatedparams));

// Confirm that the tag instance of the badge has been removed.
$this->assertFalse($DB->record_exists('tag_instance', ['itemid' => $this->badgeid]));
}

public function test_create_badge_criteria() {
Expand Down Expand Up @@ -1638,4 +1650,24 @@ private function move_backpack_to_first_position(int $backpackid): void {
$backpack = badges_get_site_backpack($backpackid);
}
}

/**
* Testing function test_badge_get_tagged_badges - search tagged badges
*
* @covers ::badge_get_tagged_badges
*/
public function test_badge_get_tagged_badges() {
$this->resetAfterTest();
$this->setAdminUser();

// Setup test data.
$badge = new badge($this->coursebadge);
\core_tag_tag::set_item_tags('core_badges', 'badge', $this->badgeid, $badge->get_context(),
['Cats', 'Dogs']);

$tag = \core_tag_tag::get_by_name(0, 'Cats');

$res = badge_get_tagged_badges($tag, false, 0, 0, 1, 0);
$this->assertStringContainsString("Test badge with 'apostrophe' and other friends (<>&@#)", $res->content);
}
}
Loading

0 comments on commit faa41cd

Please sign in to comment.