$json = ($action) ? $assertion->get_badge_class() : $assertion->get_issuer();
} else {
// Otherwise, get badge assertion.
- $json = $assertion->get_badge_assertion();
+ $column = $DB->sql_compare_text('uniquehash', 255);
+ if ($DB->record_exists_sql(sprintf('SELECT * FROM {badge_issued} WHERE %s = ?', $column), array($hash))) {
+ $json = $assertion->get_badge_assertion();
+ } else { // Revoked badge.
+ header("HTTP/1.0 410 Gone");
+ echo json_encode(array("revoked" => true));
+ die();
+ }
}
$badgeid = required_param('id', PARAM_INT);
$role = optional_param('role', 0, PARAM_INT);
$award = optional_param('award', false, PARAM_BOOL);
+$revoke = optional_param('revoke', false, PARAM_BOOL);
require_login();
}
}
+ $recipientselector->invalidate_selected_users();
+ $existingselector->invalidate_selected_users();
+ $recipientselector->set_existing_recipients($existingselector->find_users(''));
+} else if ($revoke && data_submitted() && has_capability('moodle/badges:revokebadge', $context)) {
+ require_sesskey();
+ $users = $existingselector->get_selected_users();
+
+ foreach ($users as $user) {
+ if (!process_manual_revoke($user->id, $USER->id, $issuerrole->roleid, $badgeid)) {
+ echo $OUTPUT->error_text(get_string('error:cannotrevokebadge', 'badges'));
+ }
+ }
+
$recipientselector->invalidate_selected_users();
$existingselector->invalidate_selected_users();
$recipientselector->set_existing_recipients($existingselector->find_users(''));
$PAGE->set_context(context_system::instance());
$output = $PAGE->get_renderer('core', 'badges');
-$badge = new issued_badge($id);
-
-if ($bake && ($badge->recipient->id == $USER->id)) {
- $name = str_replace(' ', '_', $badge->badgeclass['name']) . '.png';
- $filehash = badges_bake($id, $badge->badgeid, $USER->id, true);
- $fs = get_file_storage();
- $file = $fs->get_file_by_hash($filehash);
- send_stored_file($file, 0, 0, true, array('filename' => $name));
-}
-
$PAGE->set_url('/badges/badge.php', array('hash' => $id));
$PAGE->set_pagelayout('base');
$PAGE->set_title(get_string('issuedbadge', 'badges'));
-if (isloggedin()) {
- $PAGE->set_heading($badge->badgeclass['name']);
- $PAGE->navbar->add($badge->badgeclass['name']);
- if ($badge->recipient->id == $USER->id) {
- $url = new moodle_url('/badges/mybadges.php');
+$badge = new issued_badge($id);
+if (!empty($badge->recipient->id)) {
+ if ($bake && ($badge->recipient->id == $USER->id)) {
+ $name = str_replace(' ', '_', $badge->badgeclass['name']) . '.png';
+ $filehash = badges_bake($id, $badge->badgeid, $USER->id, true);
+ $fs = get_file_storage();
+ $file = $fs->get_file_by_hash($filehash);
+ send_stored_file($file, 0, 0, true, array('filename' => $name));
+ }
+
+ if (isloggedin()) {
+ $PAGE->set_heading($badge->badgeclass['name']);
+ $PAGE->navbar->add($badge->badgeclass['name']);
+ if ($badge->recipient->id == $USER->id) {
+ $url = new moodle_url('/badges/mybadges.php');
+ } else {
+ $url = new moodle_url($CFG->wwwroot);
+ }
+ navigation_node::override_active_url($url);
} else {
+ $PAGE->set_heading($badge->badgeclass['name']);
+ $PAGE->navbar->add($badge->badgeclass['name']);
$url = new moodle_url($CFG->wwwroot);
+ navigation_node::override_active_url($url);
}
- navigation_node::override_active_url($url);
-} else {
- $PAGE->set_heading($badge->badgeclass['name']);
- $PAGE->navbar->add($badge->badgeclass['name']);
- $url = new moodle_url($CFG->wwwroot);
- navigation_node::override_active_url($url);
-}
-// Include JS files for backpack support.
-badges_setup_backpack_js();
+ // Include JS files for backpack support.
+ badges_setup_backpack_js();
-echo $OUTPUT->header();
+ echo $OUTPUT->header();
-echo $output->render($badge);
+ echo $output->render($badge);
+} else {
+ echo $OUTPUT->header();
+
+ echo $OUTPUT->container($OUTPUT->error_text(get_string('error:badgeawardnotfound', 'badges')) .
+ html_writer::tag('p', $OUTPUT->close_window_button()), 'important', 'notice');
+}
// Trigger event, badge viewed.
$other = array('badgeid' => $badge->badgeid, 'badgehash' => $id);
WHERE ' . $DB->sql_compare_text('bi.uniquehash', 40) . ' = ' . $DB->sql_compare_text(':hash', 40),
array('hash' => $hash), IGNORE_MISSING);
- $this->_url = new moodle_url('/badges/badge.php', array('hash' => $this->_data->uniquehash));
+ if ($this->_data) {
+ $this->_url = new moodle_url('/badges/badge.php', array('hash' => $this->_data->uniquehash));
+ } else {
+ $this->_url = new moodle_url('/badges/badge.php');
+ }
}
/**
return true;
}
}
+ return false;
+}
+
+/**
+ * Manually revoke awarded badges.
+ *
+ * @param int $recipientid
+ * @param int $issuerid
+ * @param int $issuerrole
+ * @param int $badgeid
+ * @return bool
+ */
+function process_manual_revoke($recipientid, $issuerid, $issuerrole, $badgeid) {
+ global $DB;
+ $params = array(
+ 'badgeid' => $badgeid,
+ 'issuerid' => $issuerid,
+ 'issuerrole' => $issuerrole,
+ 'recipientid' => $recipientid
+ );
+ if ($DB->record_exists('badge_manual_award', $params)) {
+ if ($DB->delete_records('badge_manual_award', array('badgeid' => $badgeid,
+ 'issuerid' => $issuerid,
+ 'recipientid' => $recipientid))
+ && $DB->delete_records('badge_issued', array('badgeid' => $badgeid,
+ 'userid' => $recipientid))) {
+ // Trigger event, badge revoked.
+ $badge = new \badge($badgeid);
+ $eventparams = array(
+ 'objectid' => $badgeid,
+ 'relateduserid' => $recipientid,
+ 'context' => $badge->get_context()
+ );
+ $event = \core\event\badge_revoked::create($eventparams);
+ $event->trigger();
+
+ return true;
+ }
+ } else {
+ throw new moodle_exception('error:badgenotfound', 'badges');
+ }
return false;
}
'value' => $this->output->larrow() . ' ' . get_string('award', 'badges'),
'class' => 'actionbutton')
);
+ $actioncell->text .= html_writer::empty_tag('input', array(
+ 'type' => 'submit',
+ 'name' => 'revoke',
+ 'value' => get_string('revoke', 'badges') . ' ' . $this->output->rarrow(),
+ 'class' => 'actionbutton')
+ );
$actioncell->text .= html_writer::end_tag('div', array());
$actioncell->attributes['class'] = 'actions';
$potentialcell = new html_table_cell();
When I follow "Course 1"
Then I should see "Course Badge 2"
Then I should not see "Course Badge 1"
+
+ @javascript
+ Scenario: Revoke badge
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ | student1 | Student | 1 | student1@example.com |
+ | student2 | Student | 2 | student2@example.com |
+ And the following "courses" exist:
+ | fullname | shortname | category | groupmode |
+ | Course 1 | C1 | 0 | 1 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ | student2 | C1 | student |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I navigate to "Add a new badge" node in "Course administration > Badges"
+ And I follow "Add a new badge"
+ And I set the following fields to these values:
+ | Name | Course Badge |
+ | Description | Course badge description |
+ | issuername | Tester of course badge |
+ And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
+ And I press "Create badge"
+ And I set the field "type" to "Manual issue by role"
+ And I set the field "Teacher" to "1"
+ And I press "Save"
+ And I press "Enable access"
+ And I press "Continue"
+ And I follow "Recipients (0)"
+ And I press "Award badge"
+ And I set the field "potentialrecipients[]" to "Student 2 (student2@example.com)"
+ And I press "Award badge"
+ And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)"
+ When I press "Award badge"
+ And I follow "Course Badge"
+ Then I should see "Recipients (2)"
+ And I follow "Recipients (2)"
+ And I press "Award badge"
+ And I set the field "existingrecipients[]" to "Student 2 (student2@example.com)"
+ And I press "Revoke badge"
+ And I set the field "existingrecipients[]" to "Student 1 (student1@example.com)"
+ When I press "Revoke badge"
+ And I follow "Course Badge"
+ Then I should see "Recipients (0)"
+
$string['error:backpacknotavailable'] = 'Your site is not accessible from the Internet, so any badges issued from this site cannot be verified by external backpack services.';
$string['error:backpackloginfailed'] = 'You could not be connected to an external backpack for the following reason: {$a}';
$string['error:backpackproblem'] = 'There was a problem connecting to your backpack service provider. Please try again later.';
+$string['error:badgeawardnotfound'] = 'Cannot verify this awarded badge. This badge may have been revoked.';
+$string['error:badgenotfound'] = 'Badge not found';
$string['error:badjson'] = 'The connection attempt returned invalid data.';
$string['error:cannotact'] = 'Cannot activate the badge. ';
$string['error:cannotawardbadge'] = 'Cannot award badge to a user.';
+$string['error:cannotrevokebadge'] = 'Cannot revoke badge from a user.';
$string['error:cannotdeletecriterion'] = 'This criterion cannot be deleted. ';
$string['error:connectionunknownreason'] = 'The connection was unsuccessful but no reason was given.';
$string['error:clone'] = 'Cannot clone the badge.';
$string['eventbadgeduplicated'] = 'Badge duplicated';
$string['eventbadgeenabled'] = 'Badge enabled';
$string['eventbadgelistingviewed'] = 'Badge listing viewed';
+$string['eventbadgerevoked'] = 'Badge revoked';
$string['eventbadgeupdated'] = 'Badge updated';
$string['eventbadgeviewed'] = 'Badge viewed';
$string['evidence'] = 'Evidence';
$string['recipientidentificationproblem'] = 'Cannot find a recipient of this badge among the existing users.';
$string['recipientvalidationproblem'] = 'Current user cannot be verified as a recipient of this badge.';
$string['relative'] = 'Relative date';
+$string['revoke'] = 'Revoke badge';
$string['requiredcourse'] = 'At least one course should be added to the courseset criterion.';
$string['reviewbadge'] = 'Changes in badge access';
$string['reviewconfirm'] = '<p>This will make your badge visible to users and allow them to start earning it.</p>
$string['badges:earnbadge'] = 'Earn badge';
$string['badges:manageglobalsettings'] = 'Manage badges global settings';
$string['badges:manageownbadges'] = 'View and manage own earned badges';
+$string['badges:revokebadge'] = 'Revoke badge from a user';
$string['badges:viewawarded'] = 'View users who earned a specific badge without being able to award a badge';
$string['badges:viewbadges'] = 'View available badges without earning them';
$string['badges:viewotherbadges'] = 'View public badges in other users\' profiles';
--- /dev/null
+<?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/>.
+
+/**
+ * Badge revoked event.
+ *
+ * @property-read array $other {
+ * Extra information about event.
+ *
+ * - int expiredate: Badge expire timestamp.
+ * - int badgeissuedid: Badge issued ID.
+ * }
+ *
+ * @package core
+ * @copyright 2016 Matt Davidson
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event triggered after a badge is revoked from a user.
+ *
+ * @package core
+ * @since Moodle 3.2
+ * @copyright 2016 Matt Davidson
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class badge_revoked extends base {
+
+ /**
+ * Set basic properties for the event.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'badge';
+ $this->data['crud'] = 'c';
+ $this->data['edulevel'] = self::LEVEL_TEACHING;
+ }
+
+ /**
+ * Returns localised general event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventbadgerevoked', 'badges');
+ }
+
+ /**
+ * Returns non-localised event description with id's for admin use only.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return "The user with id '$this->relateduserid' has had the badge with id '$this->objectid' revoked.";
+ }
+
+ /**
+ * Returns relevant URL.
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/badges/overview.php', array('id' => $this->objectid));
+ }
+
+ /**
+ * Custom validations.
+ *
+ * @throws \coding_exception
+ * @return void
+ */
+ protected function validate_data() {
+ parent::validate_data();
+
+ if (!isset($this->relateduserid)) {
+ throw new \coding_exception('The \'relateduserid\' must be set.');
+ }
+
+ if (!isset($this->objectid)) {
+ throw new \coding_exception('The \'objectid\' must be set.');
+ }
+ }
+
+ /**
+ * Get_objectid_mapping method.
+ *
+ * @return array
+ */
+ public static function get_objectid_mapping() {
+ return array('db' => 'badge', 'restore' => 'badge');
+ }
+
+ /**
+ * Get_other_mapping method.
+ *
+ * @return array
+ */
+ public static function get_other_mapping() {
+ $othermapped = array();
+ $othermapped['badgeissuedid'] = array('db' => 'badge_issued', 'restore' => base::NOT_MAPPED);
+
+ return $othermapped;
+ }
+}
)
),
+ // Revoke badge from a user.
+ 'moodle/badges:revokebadge' => array(
+ 'riskbitmask' => RISK_SPAM,
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_COURSE,
+ 'archetypes' => array(
+ 'manager' => CAP_ALLOW,
+ 'teacher' => CAP_ALLOW,
+ 'editingteacher' => CAP_ALLOW,
+ )
+ ),
+
// View users who earned a specific badge without being able to award a badge.
'moodle/badges:viewawarded' => array(
'riskbitmask' => RISK_PERSONAL,