MDL-65959 badges: Update the implementation to use admin set backpack
authorPeter Dias <peter@moodle.com>
Fri, 28 Feb 2020 05:00:15 +0000 (13:00 +0800)
committerPeter Dias <peter@moodle.com>
Mon, 26 Oct 2020 16:27:19 +0000 (00:27 +0800)
badges/classes/backpack_api.php
badges/classes/form/backpack.php
badges/classes/form/external_backpack.php
badges/mybackpack.php
lang/en/badges.php
lang/en/deprecated.txt
lib/badgeslib.php
lib/db/install.xml
lib/db/upgrade.php

index d155443..7a8c098 100644 (file)
@@ -90,13 +90,10 @@ class backpack_api {
         $this->backpackapiurl = $sitebackpack->backpackapiurl;
         $this->backpackapiversion = $sitebackpack->apiversion;
         $this->password = $sitebackpack->password;
-        $this->email = !empty($CFG->badges_defaultissuercontact) ? $CFG->badges_defaultissuercontact : '';
+        $this->email = $sitebackpack->backpackemail;
         $this->isuserbackpack = false;
         $this->backpackid = $sitebackpack->id;
         if (!empty($userbackpack)) {
-            if ($userbackpack->externalbackpackid != $sitebackpack->id) {
-                throw new coding_exception('Incorrect backpack');
-            }
             $this->isuserbackpack = true;
             $this->password = $userbackpack->password;
             $this->email = $userbackpack->email;
@@ -602,7 +599,7 @@ class backpack_api {
      * @param integer $backpackid The backpack to disconnect
      * @return boolean
      */
-    public function disconnect_backpack($userid, $backpackid, $externalbackupid) {
+    public function disconnect_backpack($userid, $backpackid) {
         global $DB, $USER;
 
         if (\core\session\manager::is_loggedinas() || $userid != $USER->id) {
@@ -614,7 +611,6 @@ class backpack_api {
 
         $DB->delete_records('badge_external', array('backpackid' => $backpackid));
         $DB->delete_records('badge_backpack', array('userid' => $userid));
-        $DB->delete_records('badge_external_backpack', array('id' => $externalbackupid));
         $badgescache->delete($userid);
         return true;
     }
index 6689a4f..6310b4c 100644 (file)
@@ -47,31 +47,55 @@ class backpack extends external_backpack {
     public function definition() {
         global $USER, $PAGE, $OUTPUT, $CFG;
         $mform = $this->_form;
+        $this->_customdata['userbackpack'] = 1;
 
         $mform->addElement('html', html_writer::tag('span', '', array('class' => 'notconnected', 'id' => 'connection-error')));
         $mform->addElement('header', 'backpackheader', get_string('backpackconnection', 'badges'));
         $mform->addHelpButton('backpackheader', 'backpackconnection', 'badges');
         $mform->addElement('hidden', 'userid', $USER->id);
         $mform->setType('userid', PARAM_INT);
-        $mform->addElement('hidden', 'externalbackpackid');
-        $mform->setType('externalbackpackid', PARAM_INT);
+        $freeze = [];
         if (isset($this->_customdata['email'])) {
             // Email will be passed in when we're in the process of verifying the user's email address,
             // so set the connection status, lock the email field, and provide options to resend the verification
             // email or cancel the verification process entirely and start over.
-            $mform->hardFreeze();
+            $freeze = ['backpackemail'];
+            $mform->addElement('hidden', 'password', $this->_customdata['backpackpassword']);
+            $mform->setType('password', PARAM_RAW);
+            $mform->addElement('hidden', 'externalbackpackid', $this->_customdata['backpackid']);
+            $mform->setType('externalbackpackid', PARAM_INT);
             $status = html_writer::tag('span', get_string('backpackemailverificationpending', 'badges'),
                 array('class' => 'notconnected', 'id' => 'connection-status'));
         } else {
+            $sitebackpacks = badges_get_site_backpacks();
+            $choices = [];
+            $restrictedoptions = [];
+            foreach ($sitebackpacks as $backpack) {
+                $choices[$backpack->id] = $backpack->backpackweburl;
+                if ($backpack->apiversion == OPEN_BADGES_V2P1) {
+                    $restrictedoptions[] = $backpack->id;
+                }
+            }
+            $mform->addElement('select', 'externalbackpackid', get_string('backpackprovider', 'badges'), $choices);
+            $mform->setType('externalbackpackid', PARAM_INT);
+            $mform->setDefault('externalbackpackid', $CFG->badges_site_backpack);
+            $mform->hideIf('password', 'externalbackpackid', 'in', $restrictedoptions);
+            $mform->hideIf('backpackemail', 'externalbackpackid', 'in', $restrictedoptions);
+
             $status = html_writer::tag('span', get_string('notconnected', 'badges'),
                 array('class' => 'notconnected', 'id' => 'connection-status'));
         }
         $mform->addElement('static', 'status', get_string('status'), $status);
 
-        parent::definition();
+        $this->add_auth_fields($this->_customdata['email'] ?? $USER->email, !isset($this->_customdata['email']));
 
-        $mform->setDefault('backpackemail', $USER->email);
         $mform->setDisableShortforms(false);
+
+        // Freeze any elemnts after definition.
+        if ($freeze) {
+            $mform->freeze($freeze);
+        }
+        $this->add_action_buttons();
     }
 
     /**
@@ -81,8 +105,8 @@ class backpack extends external_backpack {
      * @param null|text $submitlabel
      */
     public function add_action_buttons($cancel = true, $submitlabel = null) {
-        if ($this->_customdata['email']) {
-            $mform = $this->_form;
+        $mform = $this->_form;
+        if (isset($this->_customdata['email'])) {
             $buttonarray = [];
             $buttonarray[] = &$mform->createElement('submit', 'submitbutton',
                                                     get_string('backpackconnectionresendemail', 'badges'));
@@ -92,27 +116,6 @@ class backpack extends external_backpack {
             $mform->closeHeaderBefore('buttonar');
         } else {
             // Email isn't present, so provide an input element to get it and a button to start the verification process.
-
-            $mform->addElement('static', 'info', get_string('backpackweburl', 'badges'), $sitebackpack->backpackweburl);
-            $mform->addElement('hidden', 'backpackid', $sitebackpack->id);
-            $mform->setType('backpackid', PARAM_INT);
-
-            $status = html_writer::tag('span', get_string('notconnected', 'badges'),
-                array('class' => 'notconnected', 'id' => 'connection-status'));
-            $mform->addElement('static', 'status', get_string('status'), $status);
-            if (badges_open_badges_backpack_api() != OPEN_BADGES_V2P1) {
-                $mform->addElement('text', 'email', get_string('email'), 'maxlength="100" size="30"');
-                $mform->addHelpButton('email', 'backpackemail', 'badges');
-                $mform->addRule('email', get_string('required'), 'required', null, 'client');
-                $mform->setType('email', PARAM_EMAIL);
-                if (badges_open_badges_backpack_api() == OPEN_BADGES_V2) {
-                    $mform->addElement('passwordunmask', 'backpackpassword', get_string('password'));
-                    $mform->setType('backpackpassword', PARAM_RAW);
-                } else {
-                    $mform->addElement('hidden', 'backpackpassword', '');
-                    $mform->setType('backpackpassword', PARAM_RAW);
-                }
-            }
             parent::add_action_buttons(false, get_string('backpackconnectionconnect', 'badges'));
         }
     }
@@ -132,9 +135,9 @@ class backpack extends external_backpack {
             $check = new stdClass();
             $check->email = $data['backpackemail'];
             $check->password = $data['password'];
-            $check->externalbackpackid = $data['externalbackpackid'];
+            $sitebackpack = badges_get_site_backpack($data['externalbackpackid']);
+            $bp = new \core_badges\backpack_api($sitebackpack, $check);
 
-            $bp = new \core_badges\backpack_api((object) $data, $check);
             $result = $bp->authenticate();
             if ($result === false || !empty($result->error)) {
                 $errors['backpackemail'] = get_string('backpackconnectionunexpectedresult', 'badges');
index 6fefec4..32390fd 100644 (file)
@@ -72,11 +72,11 @@ class external_backpack extends \moodleform {
         $mform->addElement('hidden', 'id', ($backpack->id ?? null));
         $mform->setType('id', PARAM_INT);
         $mform->addElement('hidden', 'badgebackpack', 0);
-        $mform->setType('badgebackpack', PARAM_INTEGER);
+        $mform->setType('badgebackpack', PARAM_INT);
         $mform->addElement('hidden', 'userid', 0);
-        $mform->setType('userid', PARAM_INTEGER);
+        $mform->setType('userid', PARAM_INT);
         $mform->addElement('hidden', 'backpackuid', 0);
-        $mform->setType('backpackuid', PARAM_INTEGER);
+        $mform->setType('backpackuid', PARAM_INT);
 
         $mform->addElement('advcheckbox', 'includeauthdetails', null, get_string('includeauthdetails', 'core_badges'));
         if (!empty($backpack->backpackemail) || !empty($backpack->password)) {
@@ -84,14 +84,7 @@ class external_backpack extends \moodleform {
         }
 
         $issuercontact = $CFG->badges_defaultissuercontact;
-        $mform->addElement('text', 'backpackemail', get_string('defaultissuercontact', 'core_badges'));
-        $mform->setType('backpackemail', PARAM_EMAIL);
-        $mform->setDefault('backpackemail', $issuercontact);
-
-        $mform->addElement('passwordunmask', 'password', get_string('defaultissuerpassword', 'core_badges'));
-        $mform->setType('password', PARAM_RAW);
-        $mform->addHelpButton('password', 'defaultissuerpassword', 'badges');
-        $mform->hideIf('password', 'apiversion', 'neq', 2);
+        $this->add_auth_fields($issuercontact);
 
         $oauth2options = badges_get_oauth2_service_options();
         $mform->addElement('select', 'oauth2_issuerid', get_string('oauth2issuer', 'core_badges'), $oauth2options);
@@ -102,6 +95,12 @@ class external_backpack extends \moodleform {
             $this->set_data($backpack);
         }
 
+        $mform->hideIf('includeauthdetails', 'apiversion', 'in', [OPEN_BADGES_V2P1]);
+        $mform->hideIf('backpackemail', 'includeauthdetails');
+        $mform->hideIf('backpackemail', 'apiversion', 'in', [OPEN_BADGES_V2P1]);
+        $mform->hideIf('password', 'includeauthdetails');
+        $mform->hideIf('password', 'apiversion', 'in', [1, OPEN_BADGES_V2P1]);
+
         // Disable short forms.
         $mform->setDisableShortforms();
 
@@ -151,4 +150,31 @@ class external_backpack extends \moodleform {
 
         return $data;
     }
+
+    /**
+     * Add backpack specific auth details.
+     *
+     * @param string|null $email The email addressed provided or null if it's new.
+     * @param boolean|true $includepassword Include the password field.
+     * @throws \coding_exception
+     */
+    protected function add_auth_fields(?string $email, bool $includepassword = true) {
+        $mform = $this->_form;
+        $emailstring = get_string('email');
+        $passwordstring = get_string('password');
+        if (!isset($this->_customdata['userbackpack'])) {
+            $emailstring = get_string('defaultissuercontact', 'core_badges');
+            $passwordstring = get_string('defaultissuerpassword', 'core_badges');
+        }
+
+        $mform->addElement('text', 'backpackemail', $emailstring);
+        $mform->setType('backpackemail', PARAM_EMAIL);
+        $mform->setDefault('backpackemail', $email);
+
+        if ($includepassword) {
+            $mform->addElement('passwordunmask', 'password', $passwordstring);
+            $mform->setType('password', PARAM_RAW);
+            $mform->addHelpButton('password', 'defaultissuerpassword', 'badges');
+        }
+    }
 }
index 11ce5cc..e759727 100644 (file)
@@ -64,7 +64,7 @@ if ($disconnect && $backpack) {
     } else {
         // If backpack is connected, need to select collections.
         $bp = new \core_badges\backpack_api($sitebackpack, $backpack);
-        $bp->disconnect_backpack($USER->id, $backpack->id, $sitebackpack->id);
+        $bp->disconnect_backpack($USER->id, $backpack->id);
         redirect(new moodle_url('/badges/mybackpack.php'));
     }
 }
@@ -142,8 +142,7 @@ if ($backpack) {
             badges_disconnect_user_backpack($USER->id);
             redirect(new moodle_url('/badges/mybackpack.php'));
         } else if (isset($data->backpackemail)) {
-            $newid = badges_create_site_backpack($data, true);
-            if (badges_send_verification_email($data->backpackemail, $newid, $data->password)) {
+            if (badges_send_verification_email($data->backpackemail, $data->externalbackpackid, $data->password)) {
                 $a = get_user_preferences('badges_email_verify_backpackid');
                 redirect(new moodle_url('/badges/mybackpack.php'),
                     get_string('backpackemailverifypending', 'badges', $data->backpackemail),
index a98db31..51b61a6 100644 (file)
@@ -83,7 +83,6 @@ $string['awards'] = 'Recipients';
 $string['backpackavailability'] = 'External badge verification';
 $string['backpackconnectionok'] = 'Backpack connection successfully established';
 $string['backpackconnectionnottested'] = 'The connection cannot be tested for this backpack because only Open Badges v2.0 backpacks support it.';
-$string['backpackneedsupdate'] = 'The backpack connected to this profile does not match the backpack for the site. You need to disconnect and reconnect the backpack.';
 $string['backpackavailability_help'] = 'For badge recipients to be able to prove they earned their badges from you, an external backpack service should be able to access your site and verify badges issued from it. Your site does not currently appear to be accessible, which means that badges you have already issued or will issue in the future cannot be verified.
 
 **Why am I seeing this message?**
@@ -137,6 +136,7 @@ In this area, you can select collections of badges from your backpack that you w
 $string['backpacksettings'] = 'Backpack settings';
 $string['backpackapiurl'] = 'Backpack API URL';
 $string['backpackweburl'] = 'Backpack URL';
+$string['backpackprovider'] = 'Backpack Provider';
 $string['badges'] = 'Badges';
 $string['badgedetails'] = 'Badge details';
 $string['badgeimage'] = 'Image';
@@ -571,7 +571,11 @@ $string['version'] = 'Version';
 $string['version_help'] = 'The version field may be used to keep track of the badge\'s development. If specified, the version is displayed on the badge page.';
 $string['warnexpired'] = ' (This badge has expired!)';
 $string['year'] = 'Year(s)';
+$string['includeauthdetails'] = "Include authentication details with the backpack";
 
 // Deprecated since Moodle 3.9.
 $string['editsettings'] = 'Edit settings';
 $string['sitebackpackverify'] = 'Backpack connection';
+
+// Deprecated since Moodle 3.10.
+$string['backpackneedsupdate'] = 'The backpack connected to this profile does not match the backpack for the site. You need to disconnect and reconnect the backpack.';
index 4ba2ea5..1028bfe 100644 (file)
@@ -124,3 +124,4 @@ userfilterplaceholder,core
 sitebackpackverify,core_badges
 filetypesnotwhitelisted,core_form
 modeloutputdirinfo,core_analytics
+backpackneedsupdate,core_badges
index 1fc0b46..38e42dc 100644 (file)
@@ -851,9 +851,7 @@ function badges_save_external_backpack(stdClass $data) {
     $backpack->apiversion = $data->apiversion;
     $backpack->backpackweburl = $data->backpackweburl;
     $backpack->backpackapiurl = $data->backpackapiurl;
-    $backpack->backpackemail = $data->backpackemail;
-    $backpack->password = !empty($data->password) ? $data->password : '';
-    $backpack->oauth2_issuerid = !empty($data->oauth2_issuerid) ? $data->oauth2_issuerid : '';
+    $backpack->oauth2_issuerid = $data->oauth2_issuerid ?? '';
     if (isset($data->sortorder)) {
         $backpack->sortorder = $data->sortorder;
     }
@@ -868,7 +866,6 @@ function badges_save_external_backpack(stdClass $data) {
 
     unset($data->id);
     badges_save_backpack_credentials($data);
-
     return $data->externalbackpackid;
 }
 
@@ -933,7 +930,12 @@ function badges_open_badges_backpack_api() {
 function badges_get_site_backpack($id) {
     global $DB;
 
-    return $DB->get_record('badge_external_backpack', ['id' => $id]);
+    $sql = "SELECT beb.*, bb.id AS badgebackpack, bb.password, bb.email AS backpackemail
+              FROM {badge_external_backpack} beb
+         LEFT JOIN {badge_backpack} bb ON bb.externalbackpackid = beb.id AND bb.userid=:userid
+             WHERE beb.id=:id";
+
+    return $DB->get_record_sql($sql, ['id' => $id, 'userid' => 0]);
 }
 
 /**
@@ -955,11 +957,11 @@ function badges_get_site_primary_backpack() {
 function badges_get_site_backpacks() {
     global $DB, $CFG;
 
-    $sql = "SELECT beb.*
+    $sql = "SELECT beb.*, bb.id as badgebackpack, bb.password, bb.email as backpackemail
             FROM {badge_external_backpack} beb
             LEFT JOIN {badge_backpack} bb ON bb.externalbackpackid = beb.id
-            WHERE bb.id IS NULL";
-    $all = $DB->get_records_sql($sql);
+            WHERE bb.id IS NULL OR bb.userid=:userid";
+    $all = $DB->get_records_sql($sql, ['userid' => 0]);
 
     foreach ($all as $key => $bp) {
         if ($bp->id == $CFG->badges_site_backpack) {
index 4781461..2225860 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20201007" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20201008" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
         <KEY NAME="fk_userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
+        <KEY NAME="backpackcredentials" TYPE="unique" FIELDS="userid, externalbackpackid"/>
         <KEY NAME="externalbackpack" TYPE="foreign" FIELDS="externalbackpackid" REFTABLE="badge_external_backpack" REFFIELDS="id"/>
       </KEYS>
     </TABLE>
         <FIELD NAME="backpackweburl" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="apiversion" TYPE="char" LENGTH="12" NOTNULL="true" DEFAULT="1.0" SEQUENCE="false"/>
         <FIELD NAME="sortorder" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
-        <FIELD NAME="password" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Password to login into external backpack and issue badges."/>
         <FIELD NAME="oauth2_issuerid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="OAuth 2 Issuer"/>
       </FIELDS>
       <KEYS>
index ae5aa60..7da92b5 100644 (file)
@@ -2863,10 +2863,40 @@ function xmldb_main_upgrade($oldversion) {
     }
 
     if ($oldversion < 2021052500.32) {
+        global $DB, $CFG;
+
+        $table = new xmldb_table('badge_backpack');
+        $uniquekey = new xmldb_key('backpackcredentials', XMLDB_KEY_UNIQUE, ['userid', 'externalbackpackid']);
+
+        // All external backpack providers/hosts are now exclusively stored in badge_external_backpack.
+        // All credentials are stored in badge_backpack and are unique per user, backpack.
+        if (!$dbman->find_key_name($table, $uniquekey)) {
+            $dbman->add_key($table, $uniquekey);
+        }
+
+        // If there is a current backpack set then copy it across to the new structure.
+        if ($CFG->badges_defaultissuercontact) {
+            // Get the currently used site backpacks.
+            $records = $DB->get_records_select('badge_external_backpack', "password IS NOT NULL AND password != ''");
+            $backpack = [
+                'userid' => '0',
+                'email' => $CFG->badges_defaultissuercontact,
+                'backpackuid' => -1
+            ];
+
+            // Create records corresponding to the site backpacks.
+            foreach ($records as $record) {
+                $backpack['password'] = $record->password;
+                $backpack['externalbackpackid'] = $record->id;
+                $DB->insert_record('badge_backpack', (object) $backpack);
+            }
+        }
+
+        // Drop the password field as this is moved to badge_backpack.
         $table = new xmldb_table('badge_external_backpack');
-        $field = new xmldb_field('backpackemail', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
-        if (!$dbman->field_exists($table, $field)) {
-            $dbman->add_field($table, $field);
+        $field = new xmldb_field('password', XMLDB_TYPE_CHAR, '50');
+        if ($dbman->field_exists($table, $field)) {
+            $dbman->drop_field($table, $field);
         }
 
         // Main savepoint reached.