Merge branch 'MDL-43230-master' of git://github.com/ryanwyllie/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 18 Oct 2016 20:35:53 +0000 (22:35 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 18 Oct 2016 20:35:53 +0000 (22:35 +0200)
badges/assertion.php
badges/award.php
badges/badge.php
badges/classes/assertion.php
badges/lib/awardlib.php
badges/renderer.php
badges/tests/behat/award_badge.feature
lang/en/badges.php
lang/en/role.php
lib/classes/event/badge_revoked.php [new file with mode: 0644]
lib/db/access.php

index be30714..3c3ae4a 100644 (file)
@@ -43,7 +43,14 @@ if (!is_null($action)) {
     $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();
+    }
 }
 
 
index 938c795..77195f0 100644 (file)
@@ -31,6 +31,7 @@ require_once($CFG->dirroot . '/badges/lib/awardlib.php');
 $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();
 
@@ -169,6 +170,19 @@ if ($award && data_submitted() && has_capability('moodle/badges:awardbadge', $co
         }
     }
 
+    $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(''));
index 662dc25..d21b96e 100644 (file)
@@ -34,42 +34,48 @@ $bake = optional_param('bake', 0, PARAM_BOOL);
 $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);
index ba0efb5..c5dba46 100644 (file)
@@ -73,7 +73,11 @@ class core_badges_assertion {
             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');
+        }
     }
 
     /**
index 8bccf53..4425540 100644 (file)
@@ -236,6 +236,47 @@ function process_manual_award($recipientid, $issuerid, $issuerrole, $badgeid) {
             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;
 }
index 184fef0..d23ff4e 100644 (file)
@@ -115,6 +115,12 @@ class core_badges_renderer extends plugin_renderer_base {
                     '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();
index 206ac42..bd0a070 100644 (file)
@@ -310,3 +310,51 @@ Feature: Award badges
     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)"
+
index 5d47fe9..ac1445d 100644 (file)
@@ -216,9 +216,12 @@ $string['error:backpackemailnotfound'] = 'The email \'{$a}\' is not associated w
 $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.';
@@ -259,6 +262,7 @@ $string['eventbadgedisabled'] = 'Badge disabled';
 $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';
@@ -349,6 +353,7 @@ $string['recipientdetails'] = 'Recipient details';
 $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>
index b7ede5d..27b8921 100644 (file)
@@ -76,6 +76,7 @@ $string['badges:deletebadge'] = 'Delete badges';
 $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';
diff --git a/lib/classes/event/badge_revoked.php b/lib/classes/event/badge_revoked.php
new file mode 100644 (file)
index 0000000..e2bd5e2
--- /dev/null
@@ -0,0 +1,118 @@
+<?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;
+    }
+}
index 7d83661..4a193da 100644 (file)
@@ -2025,6 +2025,18 @@ $capabilities = array(
         )
     ),
 
+    // 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,