mod/assign/feedback/editpdf/fpdi/
repository/s3/S3.php
theme/boost/scss/bootstrap/
-theme/boost/amd/src/alert.js
-theme/boost/amd/src/button.js
-theme/boost/amd/src/carousel.js
-theme/boost/amd/src/collapse.js
-theme/boost/amd/src/dropdown.js
-theme/boost/amd/src/index.js
-theme/boost/amd/src/modal.js
-theme/boost/amd/src/popover.js
-theme/boost/amd/src/sanitizer.js
-theme/boost/amd/src/scrollspy.js
-theme/boost/amd/src/tab.js
-theme/boost/amd/src/toast.js
-theme/boost/amd/src/tooltip.js
-theme/boost/amd/src/util.js
+theme/boost/amd/src/bootstrap/alert.js
+theme/boost/amd/src/bootstrap/button.js
+theme/boost/amd/src/bootstrap/carousel.js
+theme/boost/amd/src/bootstrap/collapse.js
+theme/boost/amd/src/bootstrap/dropdown.js
+theme/boost/amd/src/bootstrap/index.js
+theme/boost/amd/src/bootstrap/modal.js
+theme/boost/amd/src/bootstrap/popover.js
+theme/boost/amd/src/bootstrap/tools/sanitizer.js
+theme/boost/amd/src/bootstrap/scrollspy.js
+theme/boost/amd/src/bootstrap/tab.js
+theme/boost/amd/src/bootstrap/toast.js
+theme/boost/amd/src/bootstrap/tooltip.js
+theme/boost/amd/src/bootstrap/util.js
theme/boost/amd/src/tether.js
theme/boost/scss/fontawesome/
\ No newline at end of file
mod/assign/feedback/editpdf/fpdi/
repository/s3/S3.php
theme/boost/scss/bootstrap/
-theme/boost/amd/src/alert.js
-theme/boost/amd/src/button.js
-theme/boost/amd/src/carousel.js
-theme/boost/amd/src/collapse.js
-theme/boost/amd/src/dropdown.js
-theme/boost/amd/src/index.js
-theme/boost/amd/src/modal.js
-theme/boost/amd/src/popover.js
-theme/boost/amd/src/sanitizer.js
-theme/boost/amd/src/scrollspy.js
-theme/boost/amd/src/tab.js
-theme/boost/amd/src/toast.js
-theme/boost/amd/src/tooltip.js
-theme/boost/amd/src/util.js
+theme/boost/amd/src/bootstrap/alert.js
+theme/boost/amd/src/bootstrap/button.js
+theme/boost/amd/src/bootstrap/carousel.js
+theme/boost/amd/src/bootstrap/collapse.js
+theme/boost/amd/src/bootstrap/dropdown.js
+theme/boost/amd/src/bootstrap/index.js
+theme/boost/amd/src/bootstrap/modal.js
+theme/boost/amd/src/bootstrap/popover.js
+theme/boost/amd/src/bootstrap/tools/sanitizer.js
+theme/boost/amd/src/bootstrap/scrollspy.js
+theme/boost/amd/src/bootstrap/tab.js
+theme/boost/amd/src/bootstrap/toast.js
+theme/boost/amd/src/bootstrap/tooltip.js
+theme/boost/amd/src/bootstrap/util.js
theme/boost/amd/src/tether.js
theme/boost/scss/fontawesome/
\ No newline at end of file
array('moodle/restore:restorecourse')
)
);
- $ADMIN->add('courses',
- new admin_externalpage('activitychooser', new lang_string('activitychooserrecommendations', 'course'),
- new moodle_url('/course/recommendations.php'),
- array('moodle/course:recommendactivity')
- )
- );
// Course Default Settings Page.
// NOTE: these settings must be applied after all other settings because they depend on them.
$CFG->wwwroot . '/course/pending.php', array('moodle/site:approvecourse')));
}
+ // Add a category for the Activity Chooser.
+ $ADMIN->add('courses', new admin_category('activitychooser', new lang_string('activitychoosercategory', 'course')));
+ $temp = new admin_settingpage('activitychoosersettings', new lang_string('activitychoosersettings', 'course'));
+ $temp->add(
+ new admin_setting_configselect(
+ 'activitychoosertabmode',
+ new lang_string('activitychoosertabmode', 'course'),
+ new lang_string('activitychoosertabmode_desc', 'course'),
+ 0,
+ [
+ 0 => new lang_string('activitychoosertabmodeone', 'course'),
+ 1 => new lang_string('activitychoosertabmodetwo', 'course'),
+ 2 => new lang_string('activitychoosertabmodethree', 'course'),
+ ]
+ )
+ );
+ $ADMIN->add('activitychooser', $temp);
+ $ADMIN->add('activitychooser',
+ new admin_externalpage('activitychooserrecommended', new lang_string('activitychooserrecommendations', 'course'),
+ new moodle_url('/course/recommendations.php'),
+ array('moodle/course:recommendactivity')
+ )
+ );
+
// Add a category for backups.
$ADMIN->add('courses', new admin_category('backups', new lang_string('backups','admin')));
--- /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/>.
+
+/**
+ * This file defines the settings pages for licenses.
+ *
+ * @package core
+ * @copyright 2020 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/licenselib.php');
+
+if ($hassiteconfig) {
+
+ $temp = new admin_settingpage('licensesettings', new lang_string('licensesettings', 'admin'));
+
+ $licenses = license_manager::get_active_licenses_as_array();
+
+ $temp->add(new admin_setting_configselect('sitedefaultlicense',
+ new lang_string('configsitedefaultlicense', 'admin'),
+ new lang_string('configsitedefaultlicensehelp', 'admin'),
+ 'unknown',
+ $licenses));
+ $temp->add(new admin_setting_configcheckbox('rememberuserlicensepref',
+ new lang_string('rememberuserlicensepref', 'admin'),
+ new lang_string('rememberuserlicensepref_help', 'admin'),
+ 1));
+ $ADMIN->add('license', $temp);
+}
$plugin->load_settings($ADMIN, 'mlbackendsettings', $hassiteconfig);
}
-/// License types
- $ADMIN->add('modules', new admin_category('licensesettings', new lang_string('licenses')));
- $temp = new admin_settingpage('managelicenses', new lang_string('managelicenses', 'admin'));
-
- require_once($CFG->libdir . '/licenselib.php');
- $licenses = array();
- $array = explode(',', $CFG->licenses);
- foreach ($array as $value) {
- $licenses[$value] = new lang_string($value, 'license');
- }
- $temp->add(new admin_setting_configselect('sitedefaultlicense', new lang_string('configsitedefaultlicense','admin'), new lang_string('configsitedefaultlicensehelp','admin'), 'allrightsreserved', $licenses));
- $temp->add(new admin_setting_managelicenses());
- $ADMIN->add('licensesettings', $temp);
-
/// Filter plugins
$ADMIN->add('modules', new admin_category('filtersettings', new lang_string('managefilters')));
$ADMIN->add('root', new admin_category('competencies', new lang_string('competencies', 'core_competency')));
$ADMIN->add('root', new admin_category('badges', new lang_string('badges'), empty($CFG->enablebadges)));
$ADMIN->add('root', new admin_category('h5p', new lang_string('h5p', 'core_h5p')));
+$ADMIN->add('root', new admin_category('license', new lang_string('license')));
$ADMIN->add('root', new admin_category('location', new lang_string('location','admin')));
$ADMIN->add('root', new admin_category('language', new lang_string('language')));
$ADMIN->add('root', new admin_category('messaging', new lang_string('messagingcategory', 'admin')));
And I press "Add category"
And I set the field "Name" to "Category 1"
And I set the field "Description" to "Category 1 description"
- When I click on "Save" "button" in the "Delete category" "dialogue"
+ When I click on "Save" "button" in the "Add category" "dialogue"
Then I should see "Category 1" in the "List of data categories" "table"
And I should see "Category 1 description" in the "Category 1" "table_row"
And I choose "Delete" in the open action menu
And I should see "Delete category"
And I should see "Are you sure you want to delete the category 'Category 1'?"
- When I click on "Delete" "button" in the "Confirm" "dialogue"
+ When I click on "Delete" "button" in the "Delete category" "dialogue"
Then I should not see "Category 1" in the "List of data categories" "table"
And I choose "Delete" in the open action menu
And I should see "Delete purpose"
And I should see "Are you sure you want to delete the purpose 'Purpose 1'?"
- When I click on "Delete" "button" in the "Confirm" "dialogue"
+ When I click on "Delete" "button" in the "Delete purpose" "dialogue"
Then I should not see "Purpose 1" in the "List of data purposes" "table"
--- /dev/null
+// 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/>.
+
+/**
+ * Modal for confirming deletion of a custom license.
+ *
+ * @module tool_licensemanager/delete_license
+ * @class delete_license
+ * @package tool_licensemanager
+ * @copyright 2019 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/modal_factory', 'core/modal_events', 'core/url', 'core/str'],
+ function($, ModalFactory, ModalEvents, Url, String) {
+
+ var trigger = $('.delete-license');
+ ModalFactory.create({
+ type: ModalFactory.types.SAVE_CANCEL,
+ title: String.get_string('deletelicense', 'tool_licensemanager'),
+ body: String.get_string('deletelicenseconfirmmessage', 'tool_licensemanager'),
+ preShowCallback: function(triggerElement, modal) {
+ triggerElement = $(triggerElement);
+ let params = {
+ 'action': 'delete',
+ 'license': triggerElement.data('license')
+ };
+ modal.deleteURL = Url.relativeUrl('/admin/tool/licensemanager/index.php', params, true);
+ },
+ large: true,
+ }, trigger)
+ .done(function(modal) {
+ modal.getRoot().on(ModalEvents.save, function(e) {
+ // Stop the default save button behaviour which is to close the modal.
+ e.preventDefault();
+ // Redirect to delete url.
+ window.location.href = modal.deleteURL;
+ });
+ });
+ });
--- /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/>.
+
+/**
+ * Form for creating/updating a custom license.
+ *
+ * @package tool_licensemanager
+ * @copyright 2019 Tom Dickman <tom.dickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_licensemanager\form;
+
+use moodleform;
+use tool_licensemanager\helper;
+use tool_licensemanager\manager;
+
+defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
+
+global $CFG;
+require_once($CFG->libdir . '/formslib.php');
+
+/**
+ * Form for creating/updating a custom license.
+ *
+ * @package tool_licensemanager
+ * @copyright 2019 Tom Dickman <tom.dickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class edit_license extends moodleform {
+
+ /**
+ * @var string the action form is taking.
+ */
+ private $action;
+
+ /**
+ * @var string license shortname if editing or empty string if creating license.
+ */
+ private $licenseshortname;
+
+ /**
+ * edit_license constructor.
+ *
+ * @param string $action the license_manager action to be taken by form.
+ * @param string $licenseshortname the shortname of the license to edit.
+ */
+ public function __construct(string $action, string $licenseshortname) {
+ $this->action = $action;
+ $this->licenseshortname = $licenseshortname;
+
+ if ($action == manager::ACTION_UPDATE && !empty($licenseshortname)) {
+ parent::__construct(helper::get_update_license_url($licenseshortname));
+ } else {
+ parent::__construct(helper::get_create_license_url());
+ }
+ }
+
+ /**
+ * Form definition for creation and editing of licenses.
+ */
+ public function definition() {
+
+ $mform = $this->_form;
+
+ $mform->addElement('text', 'shortname', get_string('shortname', 'tool_licensemanager'));
+ $mform->setType('shortname', PARAM_ALPHANUMEXT);
+ // Shortname is only editable when user is creating a license.
+ if ($this->action != manager::ACTION_CREATE) {
+ $mform->freeze('shortname');
+ } else {
+ $mform->addRule('shortname', get_string('shortnamerequirederror', 'tool_licensemanager'), 'required');
+ }
+
+ $mform->addElement('text', 'fullname', get_string('fullname', 'tool_licensemanager'));
+ $mform->setType('fullname', PARAM_TEXT);
+ $mform->addRule('fullname', get_string('fullnamerequirederror', 'tool_licensemanager'), 'required');
+
+ $mform->addElement('text', 'source', get_string('source', 'tool_licensemanager'));
+ $mform->setType('source', PARAM_URL);
+ $mform->addHelpButton('source', 'source', 'tool_licensemanager');
+ $mform->addRule('source', get_string('sourcerequirederror', 'tool_licensemanager'), 'required');
+
+ $mform->addElement('date_selector', 'version', get_string('version', 'tool_licensemanager'), get_string('from'));
+ $mform->addHelpButton('version', 'version', 'tool_licensemanager');
+
+ $this->add_action_buttons();
+ }
+
+ /**
+ * Validate form data and return errors (if any).
+ *
+ * @param array $data array of ("fieldname"=>value) of submitted data
+ * @param array $files array of uploaded files "element_name"=>tmp_file_path
+ * @return array of "element_name"=>"error_description" if there are errors,
+ * or an empty array if everything is OK (true allowed for backwards compatibility too).
+ */
+ public function validation($data, $files) {
+ $errors = parent::validation($data, $files);
+
+ if (array_key_exists('source', $data) && !filter_var($data['source'], FILTER_VALIDATE_URL)) {
+ $errors['source'] = get_string('invalidurl', 'tool_licensemanager');
+ }
+
+ if (array_key_exists('version', $data) && $data['version'] > time()) {
+ $errors['version'] = get_string('versioncannotbefuture', 'tool_licensemanager');
+ }
+
+ return $errors;
+ }
+}
--- /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/>.
+
+/**
+ * License manager helper class.
+ *
+ * @package tool_licensemanager
+ * @copyright 2019 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_licensemanager;
+
+use moodle_url;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * License manager helper class.
+ *
+ * @package tool_licensemanager
+ * @copyright 2019 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class helper {
+
+ /**
+ * Moodle relative path to the licenses manager.
+ */
+ const MANAGER_PATH = '/admin/tool/licensemanager/index.php';
+
+ /**
+ * Get the URL for viewing the license manager interface.
+ *
+ * @return \moodle_url
+ */
+ public static function get_licensemanager_url() : moodle_url {
+ global $CFG;
+
+ $url = new moodle_url($CFG->wwwroot . self::MANAGER_PATH,
+ ['sesskey' => sesskey()]);
+
+ return $url;
+ }
+
+ /**
+ * Get the URL for endpoint enabling a license.
+ *
+ * @param string $licenseshortname the shortname of license to enable.
+ *
+ * @return \moodle_url
+ */
+ public static function get_enable_license_url(string $licenseshortname) : moodle_url {
+ $url = new moodle_url(self::MANAGER_PATH,
+ ['action' => manager::ACTION_ENABLE, 'license' => $licenseshortname, 'sesskey' => sesskey()]);
+
+ return $url;
+ }
+
+ /**
+ * Get the URL for endpoint disabling a license.
+ *
+ * @param string $licenseshortname the shortname of license to disable.
+ *
+ * @return \moodle_url
+ */
+ public static function get_disable_license_url(string $licenseshortname) : moodle_url {
+ $url = new moodle_url(self::MANAGER_PATH,
+ ['action' => manager::ACTION_DISABLE, 'license' => $licenseshortname, 'sesskey' => sesskey()]);
+
+ return $url;
+ }
+
+ /**
+ * Get the URL endpoint to create a new license.
+ *
+ * @return \moodle_url
+ */
+ public static function get_create_license_url() : moodle_url {
+ $url = new moodle_url(self::MANAGER_PATH,
+ ['action' => manager::ACTION_CREATE, 'sesskey' => sesskey()]);
+
+ return $url;
+ }
+
+ /**
+ * Get the URL endpoint to update an existing license.
+ *
+ * @param string $licenseshortname the shortname of license to update.
+ *
+ * @return \moodle_url
+ */
+ public static function get_update_license_url(string $licenseshortname) : moodle_url {
+ $url = new moodle_url(self::MANAGER_PATH,
+ ['action' => manager::ACTION_UPDATE, 'license' => $licenseshortname, 'sesskey' => sesskey()]);
+
+ return $url;
+ }
+
+ /**
+ * Get the URL endpoint to move a license up order.
+ *
+ * @param string $licenseshortname the shortname of license to move up.
+ *
+ * @return \moodle_url
+ */
+ public static function get_moveup_license_url(string $licenseshortname) : moodle_url {
+ $url = new moodle_url(self::MANAGER_PATH,
+ ['action' => manager::ACTION_MOVE_UP, 'license' => $licenseshortname, 'sesskey' => sesskey()]);
+
+ return $url;
+ }
+
+ /**
+ * Get the URL endpoint to move a license down order.
+ *
+ * @param string $licenseshortname the shortname of license to move down.
+ *
+ * @return \moodle_url
+ */
+ public static function get_movedown_license_url(string $licenseshortname) : moodle_url {
+ $url = new moodle_url(self::MANAGER_PATH,
+ ['action' => manager::ACTION_MOVE_DOWN, 'license' => $licenseshortname, 'sesskey' => sesskey()]);
+
+ return $url;
+ }
+
+ /**
+ * Convert a license version number string to a UNIX epoch.
+ *
+ * @param string $version
+ *
+ * @return int $epoch
+ */
+ public static function convert_version_to_epoch(string $version) : int {
+ $date = substr($version, 0, 8);
+ $epoch = strtotime($date);
+
+ return $epoch;
+ }
+}
--- /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/>.
+
+/**
+ * License manager.
+ *
+ * @package tool_licensemanager
+ * @copyright 2019 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_licensemanager;
+
+use tool_licensemanager\form\edit_license;
+use license_manager;
+use stdClass;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * License manager, main controller for tool_licensemanager.
+ *
+ * @package tool_licensemanager
+ * @copyright 2019 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class manager {
+
+ /**
+ * Action for creating a new custom license.
+ */
+ const ACTION_CREATE = 'create';
+
+ /**
+ * Action for updating a custom license's details.
+ */
+ const ACTION_UPDATE = 'update';
+
+ /**
+ * Action for deleting a custom license.
+ */
+ const ACTION_DELETE = 'delete';
+
+ /**
+ * Action for disabling a custom license.
+ */
+ const ACTION_DISABLE = 'disable';
+
+ /**
+ * Action for enabling a custom license.
+ */
+ const ACTION_ENABLE = 'enable';
+
+ /**
+ * Action for displaying the license list view.
+ */
+ const ACTION_VIEW_LICENSE_MANAGER = 'viewlicensemanager';
+
+ /**
+ * Action for moving a license up order.
+ */
+ const ACTION_MOVE_UP = 'moveup';
+
+ /**
+ * Action for moving a license down order.
+ */
+ const ACTION_MOVE_DOWN = 'movedown';
+
+ /**
+ * Entry point for internal license manager.
+ *
+ * @param string $action the api action to carry out.
+ * @param string|object $license the license object or shortname of license to carry action out on.
+ */
+ public function execute(string $action, $license) : void {
+
+ admin_externalpage_setup('licensemanager');
+
+ // Convert license to a string if it's a full license object.
+ if (is_object($license)) {
+ $license = $license->shortname;
+ }
+
+ $viewmanager = true;
+
+ switch ($action) {
+ case self::ACTION_DISABLE:
+ license_manager::disable($license);
+ break;
+
+ case self::ACTION_ENABLE:
+ license_manager::enable($license);
+ break;
+
+ case self::ACTION_DELETE:
+ license_manager::delete($license);
+ break;
+
+ case self::ACTION_CREATE:
+ case self::ACTION_UPDATE:
+ $viewmanager = $this->edit($action, $license);
+ break;
+
+ case self::ACTION_MOVE_UP:
+ case self::ACTION_MOVE_DOWN:
+ $this->change_license_order($action, $license);
+ break;
+
+ case self::ACTION_VIEW_LICENSE_MANAGER:
+ default:
+ break;
+ }
+ if ($viewmanager) {
+ $this->view_license_manager();
+ }
+ }
+
+ /**
+ * Edit an existing license or create a new license.
+ *
+ * @param string $action the form action to carry out.
+ * @param string $licenseshortname the shortname of the license to edit.
+ *
+ * @return bool true if license editing complete, false otherwise.
+ */
+ private function edit(string $action, string $licenseshortname) : bool {
+
+ if ($action != self::ACTION_CREATE && $action != self::ACTION_UPDATE) {
+ throw new \coding_exception('license edit actions are limited to create and update');
+ }
+
+ $form = new form\edit_license($action, $licenseshortname);
+
+ if ($form->is_cancelled()) {
+ return true;
+ } else if ($data = $form->get_data()) {
+
+ $license = new stdClass();
+ if ($action == self::ACTION_CREATE) {
+ // Check that license shortname isn't already in use.
+ if (!empty(license_manager::get_license_by_shortname($data->shortname))) {
+ print_error('duplicatelicenseshortname', 'tool_licensemanager',
+ helper::get_licensemanager_url(),
+ $data->shortname);
+ }
+ $license->shortname = $data->shortname;
+ } else {
+ if (empty(license_manager::get_license_by_shortname($licenseshortname))) {
+ print_error('licensenotfoundshortname', 'license',
+ helper::get_licensemanager_url(),
+ $licenseshortname);
+ }
+ $license->shortname = $licenseshortname;
+ }
+ $license->fullname = $data->fullname;
+ $license->source = $data->source;
+ // Legacy date format maintained to prevent breaking on upgrade.
+ $license->version = date('Ymd', $data->version) . '00';
+
+ license_manager::save($license);
+
+ return true;
+ } else {
+ $this->view_license_editor($action, $licenseshortname, $form);
+
+ return false;
+ }
+ }
+
+ /**
+ * Change license order by moving up or down license order.
+ *
+ * @param string $direction which direction to move, up or down.
+ * @param string $licenseshortname the shortname of the license to move up or down order.
+ */
+ private function change_license_order(string $direction, string $licenseshortname) : void {
+
+ if (!empty($licenseshortname)) {
+ if ($direction == self::ACTION_MOVE_UP) {
+ license_manager::change_license_sortorder(license_manager::LICENSE_MOVE_UP, $licenseshortname);
+ } else if ($direction == self::ACTION_MOVE_DOWN) {
+ license_manager::change_license_sortorder(license_manager::LICENSE_MOVE_DOWN, $licenseshortname);
+ }
+ }
+ }
+
+ /**
+ * View the license editor to create or edit a license.
+ *
+ * @param string $action
+ * @param string $licenseshortname the shortname of the license to create/edit.
+ * @param \tool_licensemanager\form\edit_license $form the form for submitting edit data.
+ */
+ private function view_license_editor(string $action, string $licenseshortname, edit_license $form) : void {
+ global $PAGE;
+
+ $renderer = $PAGE->get_renderer('tool_licensemanager');
+
+ if ($action == self::ACTION_UPDATE && $license = license_manager::get_license_by_shortname($licenseshortname)) {
+ $return = $renderer->render_edit_licence_headers($licenseshortname);
+
+ $form->set_data(['shortname' => $license->shortname]);
+ $form->set_data(['fullname' => $license->fullname]);
+ $form->set_data(['source' => $license->source]);
+ $form->set_data(['version' => helper::convert_version_to_epoch($license->version)]);
+
+ } else {
+ $return = $renderer->render_create_licence_headers();
+ }
+ $return .= $form->render();
+ $return .= $renderer->footer();
+
+ echo $return;
+ }
+
+ /**
+ * View the license manager.
+ */
+ private function view_license_manager() : void {
+ global $PAGE;
+
+ $PAGE->requires->js_call_amd('tool_licensemanager/delete_license');
+
+ $renderer = $PAGE->get_renderer('tool_licensemanager');
+ $html = $renderer->header();
+ $html .= $renderer->heading(get_string('licensemanager', 'tool_licensemanager'));
+
+ $table = new \tool_licensemanager\output\table();
+ $html .= $renderer->render($table);
+ $html .= $renderer->footer();
+
+ echo $html;
+ }
+}
--- /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/>.
+
+/**
+ * Renderer for 'tool_licensemanager' component.
+ *
+ * @package tool_licensemanager
+ * @copyright Tom Dickman <tomdickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_licensemanager\output;
+
+defined('MOODLE_INTERNAL') || die();
+
+use license_manager;
+use plugin_renderer_base;
+use tool_licensemanager\helper;
+
+/**
+ * Renderer class for 'tool_licensemanager' component.
+ *
+ * @package tool_licensemanager
+ * @copyright 2019 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class renderer extends plugin_renderer_base {
+
+ /**
+ * Render the headers for create license form.
+ *
+ * @return string html fragment for display.
+ */
+ public function render_create_licence_headers() : string {
+
+ $this->page->navbar->add(get_string('createlicense', 'tool_licensemanager'),
+ helper::get_create_license_url());
+
+ $return = $this->header();
+ $return .= $this->heading(get_string('createlicense', 'tool_licensemanager'));
+
+ return $return;
+ }
+
+ /**
+ * Render the headers for edit license form.
+ *
+ * @param string $licenseshortname the shortname of license to edit.
+ *
+ * @return string html fragment for display.
+ */
+ public function render_edit_licence_headers(string $licenseshortname) : string {
+
+ $this->page->navbar->add(get_string('editlicense', 'tool_licensemanager'),
+ helper::get_update_license_url($licenseshortname));
+
+ $return = $this->header();
+ $return .= $this->heading(get_string('editlicense', 'tool_licensemanager'));
+
+ return $return;
+ }
+
+ /**
+ * Render the license manager table.
+ *
+ * @param \renderable $table the renderable.
+ *
+ * @return string HTML.
+ */
+ public function render_table(\renderable $table) {
+ $licenses = license_manager::get_licenses();
+
+ // Add the create license button.
+ $html = $table->create_license_link();
+
+ // Add the table containing licenses for management.
+ $html .= $this->box_start('generalbox editorsui');
+ $html .= $table->create_license_manager_table($licenses, $this);
+ $html .= $this->box_end();
+
+ return $html;
+ }
+}
--- /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/>.
+
+/**
+ * Renderable for display of license manager table.
+ *
+ * @package tool_licensemanager
+ * @copyright 2020 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_licensemanager\output;
+
+use html_table;
+use html_table_cell;
+use html_table_row;
+use html_writer;
+use license_manager;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Renderable for display of license manager table.
+ *
+ * @package tool_licensemanager
+ * @copyright 2020 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class table implements \renderable {
+
+ /**
+ * 'Create License' link.
+ *
+ * @return string HTML string.
+ */
+ public function create_license_link() {
+ $link = html_writer::link(\tool_licensemanager\helper::get_create_license_url(),
+ get_string('createlicensebuttontext', 'tool_licensemanager'),
+ ['class' => 'btn btn-secondary mb-3']);
+
+ return $link;
+ }
+
+ /**
+ * Create the HTML table for license management.
+ *
+ * @param array $licenses
+ * @param \renderer_base $output
+ *
+ * @return string HTML for license manager table.
+ */
+ public function create_license_manager_table(array $licenses, \renderer_base $output) {
+ $table = new html_table();
+ $table->head = [
+ get_string('enable'),
+ get_string('license', 'tool_licensemanager'),
+ get_string('version'),
+ get_string('order'),
+ get_string('edit'),
+ get_string('delete'),
+ ];
+ $table->colclasses = [
+ 'text-center',
+ 'text-left',
+ 'text-left',
+ 'text-center',
+ 'text-center',
+ 'text-center',
+ ];
+ $table->id = 'manage-licenses';
+ $table->attributes['class'] = 'admintable generaltable';
+ $table->data = [];
+
+ $rownumber = 0;
+ $rowcount = count($licenses);
+
+ foreach ($licenses as $key => $value) {
+ $canmoveup = $rownumber > 0;
+ $canmovedown = $rownumber < $rowcount - 1;
+ $table->data[] = $this->get_license_table_row_data($value, $canmoveup, $canmovedown, $output);
+ $rownumber++;
+ }
+
+ $html = html_writer::table($table);
+
+ return $html;
+ }
+
+ /**
+ * Get table row data for a license.
+ *
+ * @param object $license the license to populate row data for.
+ * @param bool $canmoveup can this row move up.
+ * @param bool $canmovedown can this row move down.
+ * @param \renderer_base $output the renderer
+ *
+ * @return \html_table_row of columns values for row.
+ */
+ protected function get_license_table_row_data($license, bool $canmoveup, bool $canmovedown, \renderer_base $output) {
+ global $CFG;
+
+ $summary = $license->fullname . ' ('. $license->shortname . ')';
+ if (!empty($license->source)) {
+ $summary .= html_writer::empty_tag('br');
+ $summary .= html_writer::link($license->source, $license->source, ['target' => '_blank']);
+ }
+ $summarycell = new html_table_cell($summary);
+ $summarycell->attributes['class'] = 'license-summary';
+ $versioncell = new html_table_cell($license->version);
+ $versioncell->attributes['class'] = 'license-version';
+
+ $deletelicense = '';
+ if ($license->shortname == $CFG->sitedefaultlicense) {
+ $hideshow = $output->pix_icon('t/locked', get_string('sitedefaultlicenselock', 'tool_licensemanager'));
+ } else {
+ if ($license->enabled == license_manager::LICENSE_ENABLED) {
+ $hideshow = html_writer::link(\tool_licensemanager\helper::get_disable_license_url($license->shortname),
+ $output->pix_icon('t/hide', get_string('disablelicensename', 'tool_licensemanager', $license->fullname)));
+ } else {
+ $hideshow = html_writer::link(\tool_licensemanager\helper::get_enable_license_url($license->shortname),
+ $output->pix_icon('t/show', get_string('enablelicensename', 'tool_licensemanager', $license->fullname)));
+ }
+
+ if ($license->custom == license_manager::CUSTOM_LICENSE) {
+ // Link url is added by the JS `delete_license` modal used for confirmation of deletion, to avoid
+ // link being usable before JavaScript loads on page.
+ $deletelicense = html_writer::link('#', $output->pix_icon('i/trash',
+ get_string('deletelicensename', 'tool_licensemanager', $license->fullname)),
+ ['class' => 'delete-license', 'data-license' => $license->shortname]);
+ }
+ }
+ $hideshowcell = new html_table_cell($hideshow);
+ $hideshowcell->attributes['class'] = 'license-status';
+
+ if ($license->custom == license_manager::CUSTOM_LICENSE) {
+ $editlicense = html_writer::link(\tool_licensemanager\helper::get_update_license_url($license->shortname),
+ $output->pix_icon('t/editinline', get_string('editlicensename', 'tool_licensemanager', $license->fullname)),
+ ['class' => 'edit-license']);
+ } else {
+ $editlicense = '';
+ }
+ $editlicensecell = new html_table_cell($editlicense);
+ $editlicensecell->attributes['class'] = 'edit-license';
+
+ $spacer = $output->pix_icon('spacer', '', 'moodle', ['class' => 'iconsmall']);
+ $updown = '';
+ if ($canmoveup) {
+ $updown .= html_writer::link(\tool_licensemanager\helper::get_moveup_license_url($license->shortname),
+ $output->pix_icon('t/up', get_string('movelicenseupname', 'tool_licensemanager', $license->fullname),
+ 'moodle', ['class' => 'iconsmall']),
+ ['class' => 'move-up']) . '';
+ } else {
+ $updown .= $spacer;
+ }
+
+ if ($canmovedown) {
+ $updown .= ' '.html_writer::link(\tool_licensemanager\helper::get_movedown_license_url($license->shortname),
+ $output->pix_icon('t/down', get_string('movelicensedownname', 'tool_licensemanager', $license->fullname),
+ 'moodle', ['class' => 'iconsmall']),
+ ['class' => 'move-down']);
+ } else {
+ $updown .= $spacer;
+ }
+ $updowncell = new html_table_cell($updown);
+ $updowncell->attributes['class'] = 'license-order';
+
+ $row = new html_table_row([$hideshowcell, $summarycell, $versioncell, $updowncell, $editlicensecell, $deletelicense]);
+ $row->attributes['data-license'] = $license->shortname;
+ $row->attributes['class'] = strtolower(get_string('license', 'tool_licensemanager'));
+
+ return $row;
+ }
+}
--- /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/>.
+
+/**
+ * Privacy Subsystem implementation for tool_licensemanager implementing null_provider.
+ *
+ * @package tool_licensemanager
+ * @copyright 2019 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_licensemanager\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem implementation for tool_licensemanager implementing null_provider.
+ *
+ * @package tool_licensemanager
+ * @copyright 2019 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+ /**
+ * Get the language string identifier with the component's language
+ * file to explain why this plugin stores no data.
+ *
+ * @return string
+ */
+ public static function get_reason() : string {
+ return 'privacy:metadata';
+ }
+}
<?php
-
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
- * Allows admin to configure licenses.
+ * License manager page.
+ *
+ * @package tool_licensemanager
+ * @copyright 2019 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-require_once('../config.php');
-require_once($CFG->libdir.'/adminlib.php');
-require_once($CFG->libdir.'/licenselib.php');
+require_once('../../../config.php');
+require_once($CFG->libdir . '/adminlib.php');
+require_once($CFG->libdir . '/licenselib.php');
require_admin();
-$returnurl = "$CFG->wwwroot/$CFG->admin/settings.php?section=managelicenses";
+$returnurl = \tool_licensemanager\helper::get_licensemanager_url();
$action = optional_param('action', '', PARAM_ALPHANUMEXT);
$license = optional_param('license', '', PARAM_SAFEDIR);
-////////////////////////////////////////////////////////////////////////////////
-// process actions
-
if (!confirm_sesskey()) {
redirect($returnurl);
}
-$return = true;
-switch ($action) {
- case 'disable':
- license_manager::disable($license);
- break;
-
- case 'enable':
- license_manager::enable($license);
- break;
+// Route via the manager.
+$licensemanager = new \tool_licensemanager\manager();
+$PAGE->set_context(context_system::instance());
+$PAGE->set_url(\tool_licensemanager\helper::get_licensemanager_url());
+$PAGE->set_title(get_string('licensemanager', 'tool_licensemanager'));
- default:
- break;
-}
-
-if ($return) {
- redirect ($returnurl);
-}
+$licensemanager->execute($action, $license);
--- /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/>.
+
+/**
+ * Strings for component 'tool_licensemanager', language 'en'
+ *
+ * @package tool_licensemanager
+ * @copyright 2019 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+$string['pluginname'] = 'License manager';
+$string['createlicense'] = 'Create custom licence';
+$string['createlicensebuttontext'] = 'Create licence';
+$string['deletelicense'] = 'Delete licence';
+$string['deletelicenseconfirmmessage'] = 'Are you sure you want to delete this licence?';
+$string['deletelicensename'] = 'Delete license \'{$a}\'';
+$string['disablelicensename'] = 'Disable licence \'{$a}\'';
+$string['duplicatelicenseshortname'] = 'Licence shortname must be unique, duplicate value found.';
+$string['editlicense'] = 'Edit licence';
+$string['editlicensename'] = 'Edit licence \'{$a}\'';
+$string['enablelicensename'] = 'Enable licence \'{$a}\'';
+$string['fullname'] = 'Licence full name';
+$string['fullnamerequirederror'] = 'You must enter a full name for the licence.';
+$string['invalidurl'] = 'Invalid source URL';
+$string['license'] = 'Licence';
+$string['licensemanager'] = 'Licence manager';
+$string['movelicensedownname'] = 'Move \'{$a}\' license down order';
+$string['movelicenseupname'] = 'Move \'{$a}\' license up order';
+$string['privacy:metadata'] = 'The tool_licensemanager plugin stores no personal data.';
+$string['shortname'] = 'Licence short name';
+$string['sitedefaultlicenselock'] = 'This is the site default license. It cannot be disabled.';
+$string['shortnamerequirederror'] = 'You must enter a short name for the licence.';
+$string['source'] = 'Licence source';
+$string['source_help'] = 'The URL (with http:// or https:// prefix) where the licence terms and conditions can be found.';
+$string['sourcerequirederror'] = 'You must enter a valid URL for licence source.';
+$string['version'] = 'Licence version';
+$string['versioncannotbefuture'] = 'Licence version cannot be set to a future date.';
+$string['version_help'] = 'Publication date of the licence version being utilised.';
+
--- /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/>.
+
+/**
+ * Settings page.
+ *
+ * @package tool_licensemanager
+ * @copyright 2020 Tom Dickman <tomdickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+if ($hassiteconfig) {
+ $temp = new admin_externalpage('licensemanager',
+ get_string('licensemanager', 'tool_licensemanager'),
+ \tool_licensemanager\helper::get_licensemanager_url());
+
+ $ADMIN->add('license', $temp);
+}
--- /dev/null
+@tool @tool_licensemanager
+Feature: Delete custom licenses
+ In order to manage custom licenses
+ As an admin
+ I need to be able to delete custom licenses but not standard Moodle licenses
+
+ @javascript
+ Scenario: I can delete a custom license
+ Given I log in as "admin"
+ And I navigate to "Licence > Licence manager" in site administration
+ And I click on "Create licence" "link"
+ And I set the following fields to these values:
+ | shortname | MIT |
+ | fullname | MIT Licence |
+ | source | https://opensource.org/licenses/MIT |
+ | version[day] | 1 |
+ | version[month] | March |
+ | version[year] | 2019 |
+ And I press "Save changes"
+ And I click on "Delete" "icon" in the "MIT" "table_row"
+ When I click on "Save changes" "button" in the "Delete licence" "dialogue"
+ Then I should not see "MIT Licence" in the "manage-licenses" "table"
+
+ Scenario: I cannot delete a standard license
+ Given I log in as "admin"
+ And I navigate to "Licence > Licence manager" in site administration
+ Then I should see "Licence not specified" in the "unknown" "table_row"
+ And I should not see "Delete" in the "unknown" "table_row"
--- /dev/null
+@tool @tool_licensemanager
+Feature: Custom licences
+ In order to use custom licences
+ As an admin
+ I need to be able to add custom licences
+
+ Scenario: I am able to create custom licences
+ Given I log in as "admin"
+ And I navigate to "Licence > Licence manager" in site administration
+ And I click on "Create licence" "link"
+ And I set the following fields to these values:
+ | shortname | MIT |
+ | fullname | MIT Licence |
+ | source | https://opensource.org/licenses/MIT |
+ | version[day] | 1 |
+ | version[month] | January |
+ | version[year] | 2020 |
+ When I press "Save changes"
+ Then I should see "Licence manager"
+ And I should see "MIT Licence" in the "MIT" "table_row"
+ And I should see "https://opensource.org/licenses/MIT" in the "MIT" "table_row"
+
+ Scenario: I am only be able to make custom license with a valid url source (including scheme).
+ Given I log in as "admin"
+ And I navigate to "Licence > Licence manager" in site administration
+ And I click on "Create licence" "link"
+ And I set the following fields to these values:
+ | shortname | MIT |
+ | fullname | MIT Licence |
+ | source | opensource.org/licenses/MIT |
+ | version[day] | 1 |
+ | version[month] | January |
+ | version[year] | 2020 |
+ When I press "Save changes"
+ Then I should see "Invalid source URL"
+ And I set the following fields to these values:
+ | source | mailto:tomdickman@catalyst-au.net |
+ And I press "Save changes"
+ And I should see "Invalid source URL"
+ And I set the following fields to these values:
+ | source | https://opensource.org/licenses/MIT |
+ And I press "Save changes"
+ And I should see "Licence manager"
+ And I should see "MIT Licence" in the "MIT" "table_row"
+ And I should see "https://opensource.org/licenses/MIT" in the "MIT" "table_row"
+
+ Scenario: Custom license version format must be YYYYMMDD00
+ Given I log in as "admin"
+ And I navigate to "Licence > Licence manager" in site administration
+ And I click on "Create licence" "link"
+ And I set the following fields to these values:
+ | shortname | MIT |
+ | fullname | MIT Licence |
+ | source | https://opensource.org/licenses/MIT |
+ | version[day] | 1 |
+ | version[month] | March |
+ | version[year] | 2019 |
+ When I press "Save changes"
+ Then I should see "Licence manager"
+ And I should see "2019030100" in the "MIT" "table_row"
+
+ @javascript
+ Scenario: Custom license short name should not be editable after first creation
+ Given I log in as "admin"
+ And I navigate to "Licence > Licence manager" in site administration
+ And I click on "Create licence" "link"
+ And I set the following fields to these values:
+ | shortname | MIT |
+ | fullname | MIT Licence |
+ | source | https://opensource.org/licenses/MIT |
+ | version[day] | 1 |
+ | version[month] | March |
+ | version[year] | 2019 |
+ And I press "Save changes"
+ And I should see "Licence manager"
+ And I should see "MIT Licence" in the "MIT" "table_row"
+ When I click on "Edit" "icon" in the "MIT" "table_row"
+ Then I should see "Edit licence"
+ And the "shortname" "field" should be disabled
--- /dev/null
+@tool @tool_licensemanager
+Feature: License manager
+ In order to manage licenses
+ As an admin
+ I need to be able to view and alter licence preferences in the license manager.
+
+ Scenario: I should be able to see the default Moodle licences.
+ Given I log in as "admin"
+ When I navigate to "Licence > Licence manager" in site administration
+ Then I should see "Licence not specified" in the "unknown" "table_row"
+ And I should see "All rights reserved" in the "allrightsreserved" "table_row"
+ And I should see "Public domain" in the "public" "table_row"
+ And I should see "Creative Commons" in the "cc" "table_row"
+ And I should see "Creative Commons - NoDerivs" in the "cc-nd" "table_row"
+ And I should see "Creative Commons - No Commercial NoDerivs" in the "cc-nc-nd" "table_row"
+ And I should see "Creative Commons - No Commercial" in the "cc-nc" "table_row"
+ And I should see "Creative Commons - No Commercial ShareAlike" in the "cc-nc-sa" "table_row"
+ And I should see "Creative Commons - ShareAlike" in the "cc-sa" "table_row"
+
+ Scenario: I should be able to enable and disable licenses
+ Given I log in as "admin"
+ And I navigate to "Licence > Licence settings" in site administration
+ When I set the field "Default site licence" to "Public domain"
+ And I press "Save changes"
+ And I navigate to "Licence > Licence manager" in site administration
+ Then "This is the site default license" "icon" should exist in the "public" "table_row"
+ And "Enable license" "icon" should not exist in the "public" "table_row"
+ And "This is the site default license" "icon" should not exist in the "cc" "table_row"
+ And I navigate to "Licence > Licence settings" in site administration
+ And I set the field "Default site licence" to "Creative Commons"
+ And I press "Save changes"
+ And I navigate to "Licence > Licence manager" in site administration
+ And "This is the site default license" "icon" should exist in the "cc" "table_row"
+ And "Enable license" "icon" should not exist in the "cc" "table_row"
+ And "This is the site default license" "icon" should not exist in the "public" "table_row"
--- /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/>.
+
+/**
+ * Tests for tool_licensemanager helper class.
+ *
+ * @package tool_licensemanager
+ * @copyright 2020 Tom Dickman <tom.dickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Tests for tool_licensemanager helper class.
+ *
+ * @package tool_licensemanager
+ * @copyright 2020 Tom Dickman <tom.dickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @group tool_licensemanager
+ */
+class helper_test extends advanced_testcase {
+
+ public function test_convert_version_to_epoch() {
+
+ $version = '2020010100';
+ $expected = strtotime(20200101);
+
+ $this->assertEquals($expected, \tool_licensemanager\helper::convert_version_to_epoch($version));
+ }
+}
--- /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/>.
+
+/**
+ * Tests for tool_licensemanager manager class.
+ *
+ * @package tool_licensemanager
+ * @copyright 2020 Tom Dickman <tom.dickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/formslib.php');
+require_once($CFG->libdir . '/licenselib.php');
+
+/**
+ * Tests for tool_licensemanager manager class.
+ *
+ * @package tool_licensemanager
+ * @copyright 2020 Tom Dickman <tom.dickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @group tool_licensemanager
+ */
+class manager_test extends advanced_testcase {
+
+ /**
+ * Test editing a license.
+ */
+ public function test_edit_existing_license() {
+ $this->resetAfterTest();
+
+ // Create initial custom license to edit.
+ $testlicense = new stdClass();
+ $testlicense->shortname = 'my-lic';
+ $testlicense->fullname = 'My License';
+ $testlicense->source = 'https://fakeurl.net';
+ $testlicense->version = date('Ymd', time()) . '00';
+ $testlicense->custom = license_manager::CUSTOM_LICENSE;
+
+ license_manager::save($testlicense);
+ license_manager::enable($testlicense->shortname);
+
+ $manager = new \tool_licensemanager\manager();
+
+ // Attempt to submit form data with altered details.
+ $formdata = [
+ 'shortname' => 'new-value',
+ 'fullname' => 'New License Name',
+ 'source' => 'https://updatedfakeurl.net',
+ 'version' => time()
+ ];
+
+ // Attempt to submit form data with an altered shortname.
+ \tool_licensemanager\form\edit_license::mock_submit($formdata);
+
+ // We're testing a private method, so we need to setup reflector magic.
+ $method = new ReflectionMethod('\tool_licensemanager\manager', 'edit');
+ $method->setAccessible(true); // Allow accessing of private method.
+ $method->invoke($manager, \tool_licensemanager\manager::ACTION_UPDATE, $testlicense->shortname);
+
+ // Should not create a new license when updating an existing license.
+ $this->assertEmpty(license_manager::get_license_by_shortname($formdata['shortname']));
+
+ $actual = license_manager::get_license_by_shortname('my-lic');
+ // Should not be able to update the shortname of the license.
+ $this->assertNotSame($formdata['shortname'], $actual->shortname);
+ // Should be able to update other details of the license.
+ $this->assertSame($formdata['fullname'], $actual->fullname);
+ $this->assertSame($formdata['source'], $actual->source);
+ $this->assertSame(date('Ymd', $formdata['version']) . '00', $actual->version);
+ }
+
+ public function test_edit_license_not_exists() {
+ $manager = new \tool_licensemanager\manager();
+
+ // We're testing a private method, so we need to setup reflector magic.
+ $method = new ReflectionMethod('\tool_licensemanager\manager', 'edit');
+ $method->setAccessible(true); // Allow accessing of private method.
+
+ // Attempt to update a license that doesn't exist.
+ $formdata = [
+ 'shortname' => 'new-value',
+ 'fullname' => 'New License Name',
+ 'source' => 'https://updatedfakeurl.net',
+ 'version' => time()
+ ];
+ \tool_licensemanager\form\edit_license::mock_submit($formdata);
+
+ // Should not be able to update a license with a shortname that doesn't exist.
+ $this->expectException('moodle_exception');
+ $method->invoke($manager, \tool_licensemanager\manager::ACTION_UPDATE, $formdata['shortname']);
+ }
+
+ public function test_edit_license_no_shortname() {
+ $manager = new \tool_licensemanager\manager();
+
+ // We're testing a private method, so we need to setup reflector magic.
+ $method = new ReflectionMethod('\tool_licensemanager\manager', 'edit');
+ $method->setAccessible(true); // Allow accessing of private method.
+
+ // Attempt to update a license without passing license shortname.
+ $formdata = [
+ 'fullname' => 'New License Name',
+ 'source' => 'https://updatedfakeurl.net',
+ 'version' => time()
+ ];
+ \tool_licensemanager\form\edit_license::mock_submit($formdata);
+
+ // Should not be able to update empty license shortname.
+ $this->expectException('moodle_exception');
+ $method->invoke($manager, \tool_licensemanager\manager::ACTION_UPDATE, '');
+ }
+
+ /**
+ * Test creating a new license.
+ */
+ public function test_edit_create_license() {
+ $this->resetAfterTest();
+
+ $licensecount = count(license_manager::get_licenses());
+
+ $manager = new \tool_licensemanager\manager();
+
+ $formdata = [
+ 'shortname' => 'new-value',
+ 'fullname' => 'My License',
+ 'source' => 'https://fakeurl.net',
+ 'version' => time()
+ ];
+
+ // Attempt to submit form data for a new license.
+ \tool_licensemanager\form\edit_license::mock_submit($formdata);
+
+ // We're testing a private method, so we need to setup reflector magic.
+ $method = new ReflectionMethod('\tool_licensemanager\manager', 'edit');
+ $method->setAccessible(true); // Allow accessing of private method.
+ $method->invoke($manager, \tool_licensemanager\manager::ACTION_CREATE, $formdata['shortname']);
+
+ // Should create a new license in database.
+ $this->assertCount($licensecount + 1, license_manager::get_licenses());
+ $actual = license_manager::get_license_by_shortname($formdata['shortname']);
+ $this->assertSame($formdata['shortname'], $actual->shortname);
+ $this->assertSame($formdata['fullname'], $actual->fullname);
+ $this->assertSame($formdata['source'], $actual->source);
+ $this->assertSame(date('Ymd', $formdata['version']) . '00', $actual->version);
+
+ // Attempt to submit form data for a duplicate license.
+ \tool_licensemanager\form\edit_license::mock_submit($formdata);
+
+ // Should not be able to create duplicate licenses.
+ $this->expectException('moodle_exception');
+ $method->invoke($manager, \tool_licensemanager\manager::ACTION_CREATE, $formdata['shortname']);
+ }
+
+ /**
+ * Test changing the order of licenses.
+ */
+ public function test_change_license_order() {
+ $this->resetAfterTest();
+
+ $licenseorder = array_keys(license_manager::get_licenses());
+ $initialposition = array_search('cc-nc', $licenseorder);
+
+ $manager = new tool_licensemanager\manager();
+
+ // We're testing a private method, so we need to setup reflector magic.
+ $method = new ReflectionMethod('\tool_licensemanager\manager', 'change_license_order');
+ $method->setAccessible(true); // Allow accessing of private method.
+ $method->invoke($manager, \tool_licensemanager\manager::ACTION_MOVE_UP, 'cc-nc');
+
+ $licenseorder = array_keys(license_manager::get_licenses());
+ $newposition = array_search('cc-nc', $licenseorder);
+
+ $this->assertLessThan($initialposition, $newposition);
+
+ $initialposition = array_search('allrightsreserved', $licenseorder);
+ $method->invoke($manager, \tool_licensemanager\manager::ACTION_MOVE_DOWN, 'allrightsreserved');
+ $licenseorder = array_keys(license_manager::get_licenses());
+ $newposition = array_search('cc-nc', $licenseorder);
+
+ $this->assertGreaterThan($initialposition, $newposition);
+ }
+
+}
--- /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/>.
+
+/**
+ * Version details for component 'tool_licensemanager'.
+ *
+ * @package tool_licensemanager
+ * @copyright Tom Dickman <tomdickman@catalyst-au.net>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version = 2020050600;
+$plugin->requires = 2020050200; // Requires this Moodle version.
+$plugin->component = 'tool_licensemanager';
+
+$plugin->maturity = MATURITY_STABLE;
You can also choose to display the courses in a list, or with summary information, or the default \'card\' view.';
$string['tour3_title_displayoptions'] = 'Display options';
-$string['tour3_content_displayoptions'] = 'Courses may be sorted by course name or by last access date.
+$string['tour3_content_displayoptions'] = 'Courses may be sorted by course name, course short name or last access date.
You can also choose to display the courses in a list, with summary information, or the default \'card\' view.';
Scenario: Delete model
When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
And I choose "Delete" in the open action menu
- And I click on "Delete" "button" in the "Confirm" "dialogue"
+ And I click on "Delete" "button" in the "Delete" "dialogue"
Then I should not see "Students at risk of not meeting the course completion conditions"
$bc = new \backup_controller(\backup::TYPE_1COURSE, $course->id, \backup::FORMAT_MOODLE,
\backup::INTERACTIVE_NO, \backup::MODE_COPY, $USER->id, \backup::RELEASESESSION_YES);
$backupid = $bc->get_backupid();
+ $bc->destroy();
$copyrec = \async_helper::get_backup_record($backupid);
$this->assertEquals($backupid, $copyrec->backupid);
$this->assertFalse($ispending);
// Create the initial backupcontoller.
- new \backup_controller(\backup::TYPE_1COURSE, $course->id, \backup::FORMAT_MOODLE,
+ $bc = new \backup_controller(\backup::TYPE_1COURSE, $course->id, \backup::FORMAT_MOODLE,
\backup::INTERACTIVE_NO, \backup::MODE_ASYNC, $USER->id, \backup::RELEASESESSION_YES);
+ $bc->destroy();
$ispending = async_helper::is_async_pending($course->id, 'course', 'backup');
// Should be false as there as async backup is false.
$this->assertFalse($ispending);
// Create the initial backupcontoller.
- new \backup_controller(\backup::TYPE_1COURSE, $course->id, \backup::FORMAT_MOODLE,
+ $bc = new \backup_controller(\backup::TYPE_1COURSE, $course->id, \backup::FORMAT_MOODLE,
\backup::INTERACTIVE_NO, \backup::MODE_COPY, $USER->id, \backup::RELEASESESSION_YES);
+ $bc->destroy();
$ispending = async_helper::is_async_pending($course->id, 'course', 'backup');
// Should be True as this a copy operation.
--- /dev/null
+// 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/>.
+
+/**
+ * Action methods related to backpacks.
+ *
+ * @module core_badges/backpackactions
+ * @package core_badges
+ * @copyright 2020 Sara Arjona <sara@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import $ from 'jquery';
+import selectors from 'core_badges/selectors';
+import {get_string as getString} from 'core/str';
+import Pending from 'core/pending';
+import ModalFactory from 'core/modal_factory';
+import ModalEvents from 'core/modal_events';
+import Config from 'core/config';
+
+/**
+ * Set up the actions.
+ *
+ * @method init
+ */
+export const init = () => {
+ const pendingPromise = new Pending();
+
+ const root = $(selectors.elements.main);
+ registerListenerEvents(root);
+
+ pendingPromise.resolve();
+};
+
+/**
+ * Register backpack related event listeners.
+ *
+ * @method registerListenerEvents
+ * @param {Object} root The root element.
+ */
+const registerListenerEvents = (root) => {
+
+ root.on('click', selectors.actions.deletebackpack, async(e) => {
+ e.preventDefault();
+
+ const link = $(e.currentTarget);
+ const modal = await buildModal(link);
+
+ displayModal(modal, link);
+ });
+};
+
+const buildModal = async(link) => {
+
+ const backpackurl = link.closest(selectors.elements.backpackurl).attr('data-backpackurl');
+
+ return ModalFactory.create({
+ title: await getString('delexternalbackpack', 'core_badges'),
+ body: await getString('delexternalbackpackconfirm', 'core_badges', backpackurl),
+ type: ModalFactory.types.SAVE_CANCEL,
+ });
+
+};
+
+const displayModal = async(modal, link) => {
+ modal.setSaveButtonText(await getString('delete', 'core'));
+
+ modal.getRoot().on(ModalEvents.save, function() {
+ window.location.href = link.attr('href') + '&sesskey=' + Config.sesskey + '&confirm=1';
+ });
+
+ modal.getRoot().on(ModalEvents.hidden, function() {
+ modal.destroy();
+ });
+
+ modal.show();
+};
--- /dev/null
+// 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/>.
+
+/**
+ * Define all of the selectors we will be using on the backpack interface.
+ *
+ * @module core_badges/selectors
+ * @package core_badges
+ * @copyright 2020 Sara Arjona <sara@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * A small helper function to build queryable data selectors.
+ *
+ * @method getDataSelector
+ * @param {String} name
+ * @param {String} value
+ * @return {string}
+ */
+const getDataSelector = (name, value) => {
+ return `[data-${name}="${value}"]`;
+};
+
+export default {
+ actions: {
+ deletebackpack: getDataSelector('action', 'deletebackpack'),
+ },
+ elements: {
+ clearsearch: '.input-group-append .clear-icon',
+ main: '#backpacklist',
+ backpackurl: '[data-backpackurl]',
+ },
+};
--- /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/>.
+
+/**
+ * Connect to backpack site.
+ *
+ * @package core_badges
+ * @copyright 2020 Tung Thai
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
+ */
+
+require_once(__DIR__ . '/../config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+
+$scope = optional_param('scope', '', PARAM_RAW);
+$action = optional_param('action', null, PARAM_RAW);
+
+if (badges_open_badges_backpack_api() != OPEN_BADGES_V2P1) {
+ throw new coding_exception('backpacks only support Open Badges V2.1');
+}
+
+require_login();
+
+$externalbackpack = badges_get_site_backpack($CFG->badges_site_backpack);
+$persistedissuer = \core\oauth2\issuer::get_record(['id' => $externalbackpack->oauth2_issuerid]);
+if ($persistedissuer) {
+ $issuer = new \core\oauth2\issuer($externalbackpack->oauth2_issuerid);
+ $returnurl = new moodle_url('/badges/backpack-connect.php',
+ ['action' => 'authorization', 'sesskey' => sesskey()]);
+
+ $client = new core_badges\oauth2\client($issuer, $returnurl, $scope, $externalbackpack);
+ if ($client) {
+ if (!$client->is_logged_in()) {
+ redirect($client->get_login_url());
+ }
+ $wantsurl = new moodle_url('/badges/mybadges.php');
+ $auth = new \core_badges\oauth2\auth();
+ $auth->complete_data($client, $wantsurl);
+ } else {
+ throw new moodle_exception('Could not get an OAuth client.');
+ }
+} else {
+ throw new moodle_exception('Unknown OAuth client.');
+}
--- /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/>.
+
+/**
+ * Export badges to the backpack site.
+ *
+ * @package core_badges
+ * @copyright 2020 Tung Thai
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
+ */
+require_once(__DIR__ . '/../config.php');
+require_once($CFG->libdir . '/badgeslib.php');
+
+if (badges_open_badges_backpack_api() != OPEN_BADGES_V2P1) {
+ throw new coding_exception('backpacks only support Open Badges V2.1');
+}
+$hash = optional_param('hash', null, PARAM_RAW);
+
+$PAGE->set_pagelayout('admin');
+$url = new moodle_url('/badges/backpack-export.php');
+
+require_login();
+if (empty($CFG->badges_allowexternalbackpack) || empty($CFG->enablebadges)) {
+ redirect($CFG->wwwroot);
+}
+$backpack = badges_get_site_backpack($CFG->badges_site_backpack);
+$userbadges = badges_get_user_badges($USER->id);
+$context = context_user::instance($USER->id);
+
+$PAGE->set_context($context);
+$PAGE->set_url($url);
+$title = get_string('badges', 'badges');
+$PAGE->set_title($title);
+$PAGE->set_heading(fullname($USER));
+$PAGE->set_pagelayout('standard');
+
+$redirecturl = new moodle_url('/badges/mybadges.php');
+if ($hash) {
+ $backpack = badges_get_site_backpack($CFG->badges_site_backpack);
+ $api = new core_badges\backpack_api2p1($backpack);
+ $notify = $api->put_assertions($hash);
+ if (!empty($notify['status']) && $notify['status'] == \core\output\notification::NOTIFY_SUCCESS) {
+ redirect($redirecturl, $notify['message'], null, \core\output\notification::NOTIFY_SUCCESS);
+ } else if (!empty($notify['status']) && $notify['status'] == \core\output\notification::NOTIFY_ERROR) {
+ redirect($redirecturl, $notify['message'], null, \core\output\notification::NOTIFY_ERROR);
+ }
+}
+redirect($redirecturl);
\ No newline at end of file
$id = optional_param('id', 0, PARAM_INT);
$action = optional_param('action', '', PARAM_ALPHA);
+$confirm = optional_param('confirm', 1, PARAM_BOOL);
$PAGE->set_pagelayout('admin');
$url = new moodle_url('/badges/backpacks.php');
$PAGE->set_url($url);
$PAGE->set_title(get_string('managebackpacks', 'badges'));
$PAGE->set_heading($SITE->fullname);
+
+$msg = '';
+$msgtype = 'error';
+if ($action == 'delete' && $confirm && confirm_sesskey()) {
+ if (badges_delete_site_backpack($id)) {
+ $msg = get_string('sitebackpackdeleted', 'badges');
+ $msgtype = 'notifysuccess';
+ } else {
+ $msg = get_string('sitebackpacknotdeleted', 'badges');
+ }
+}
+
if ($action == 'edit') {
$backpack = null;
if (!empty($id)) {
echo $OUTPUT->header();
echo $output->heading(get_string('managebackpacks', 'badges'));
+ if ($msg) {
+ echo $OUTPUT->notification($msg, $msgtype);
+ }
$page = new \core_badges\output\external_backpacks_page($url);
echo $output->render($page);
}
--- /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/>.
+
+/**
+ * Communicate with backpacks.
+ *
+ * @copyright 2020 Tung Thai based on Totara Learning Solutions Ltd {@link http://www.totaralms.com/} dode
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
+ */
+
+namespace core_badges;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/filelib.php');
+
+use cache;
+use coding_exception;
+use context_system;
+use moodle_url;
+use core_badges\backpack_api2p1_mapping;
+use core_badges\oauth2\client;
+use curl;
+use stdClass;
+
+/**
+ * To process badges with backpack and control api request and this class using for Open Badge API v2.1 methods.
+ *
+ * @package core_badges
+ * @copyright 2020 Tung Thai
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class backpack_api2p1 {
+
+ /** @var object is the external backpack. */
+ private $externalbackpack;
+
+ /** @var array define api mapping. */
+ private $mappings = [];
+
+ /** @var false|null|stdClass|\core_badges\backpack_api2p1 to */
+ private $tokendata;
+
+ /** @var null clienid. */
+ private $clientid = null;
+
+ /** @var null version api of the backpack. */
+ protected $backpackapiversion;
+
+ /** @var null api URL of the backpack. */
+ protected $backpackapiurl = '';
+
+ /**
+ * backpack_api2p1 constructor.
+ *
+ * @param object $externalbackpack object
+ * @throws coding_exception error message
+ */
+ public function __construct($externalbackpack) {
+
+ if (!empty($externalbackpack)) {
+ $this->externalbackpack = $externalbackpack;
+ $this->backpackapiversion = $externalbackpack->apiversion;
+ $this->backpackapiurl = $externalbackpack->backpackapiurl;
+ $this->get_clientid = $this->get_clientid($externalbackpack->oauth2_issuerid);
+
+ if (!($this->tokendata = $this->get_stored_token($externalbackpack->id))
+ && $this->backpackapiversion != OPEN_BADGES_V2P1) {
+ throw new coding_exception('Backpack incorrect');
+ }
+ }
+
+ $this->define_mappings();
+ }
+
+
+ /**
+ * Define the mappings supported by this usage and api version.
+ */
+ private function define_mappings() {
+ if ($this->backpackapiversion == OPEN_BADGES_V2P1) {
+
+ $mapping = [];
+ $mapping[] = [
+ 'post.assertions', // Action.
+ '[URL]/assertions', // URL
+ '[PARAM]', // Post params.
+ false, // Multiple.
+ 'post', // Method.
+ true, // JSON Encoded.
+ true // Auth required.
+ ];
+
+ $mapping[] = [
+ 'get.assertions', // Action.
+ '[URL]/assertions', // URL
+ '[PARAM]', // Post params.
+ false, // Multiple.
+ 'get', // Method.
+ true, // JSON Encoded.
+ true // Auth required.
+ ];
+
+ foreach ($mapping as $map) {
+ $map[] = false; // Site api function.
+ $map[] = OPEN_BADGES_V2P1; // V2 function.
+ $this->mappings[] = new backpack_api2p1_mapping(...$map);
+ }
+
+ }
+ }
+
+ /**
+ * Disconnect the backpack from this user.
+ *
+ * @param object $backpack to disconnect.
+ * @return bool
+ * @throws \dml_exception
+ */
+ public function disconnect_backpack($backpack) {
+ global $USER, $DB;
+
+ if ($backpack) {
+ $DB->delete_records_select('badge_external', 'backpackid = :backpack', ['backpack' => $backpack->id]);
+ $DB->delete_records('badge_backpack', ['id' => $backpack->id]);
+ $DB->delete_records('badge_backpack_oauth2', ['externalbackpackid' => $this->externalbackpack->id,
+ 'userid' => $USER->id]);
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Make an api request.
+ *
+ * @param string $action The api function.
+ * @param string $postdata The body of the api request.
+ * @return mixed
+ */
+ public function curl_request($action, $postdata = null) {
+ $tokenkey = $this->tokendata->token;
+ foreach ($this->mappings as $mapping) {
+ if ($mapping->is_match($action)) {
+ return $mapping->request(
+ $this->backpackapiurl,
+ $tokenkey,
+ $postdata
+ );
+ }
+ }
+
+ throw new coding_exception('Unknown request');
+ }
+
+ /**
+ * Get token.
+ *
+ * @param int $externalbackpackid ID of external backpack.
+ * @return oauth2\badge_backpack_oauth2|false|stdClass|null
+ */
+ protected function get_stored_token($externalbackpackid) {
+ global $USER;
+
+ $token = \core_badges\oauth2\badge_backpack_oauth2::get_record(
+ ['externalbackpackid' => $externalbackpackid, 'userid' => $USER->id]);
+ if ($token !== false) {
+ $token = $token->to_record();
+ return $token;
+ }
+ return null;
+ }
+
+ /**
+ * Get client id.
+ *
+ * @param int $issuerid id of Oauth2 service.
+ * @throws coding_exception
+ */
+ private function get_clientid($issuerid) {
+ $issuer = \core\oauth2\api::get_issuer($issuerid);
+ if (!empty($issuer)) {
+ $this->clientid = $issuer->get('clientid');
+ }
+ }
+
+ /**
+ * Export a badge to the backpack site.
+ *
+ * @param string $hash of badge issued.
+ * @return array
+ * @throws \moodle_exception
+ * @throws coding_exception
+ */
+ public function put_assertions($hash) {
+ $data = [];
+ if (!$hash) {
+ return false;
+ }
+
+ $issuer = new \core\oauth2\issuer($this->externalbackpack->oauth2_issuerid);
+ $client = new client($issuer, new moodle_url('/badges/mybadges.php'), '', $this->externalbackpack);
+ if (!$client->is_logged_in()) {
+ $redirecturl = new moodle_url('/badges/mybadges.php', ['error' => 'backpackexporterror']);
+ redirect($redirecturl);
+ }
+
+ $this->tokendata = $this->get_stored_token($this->externalbackpack->id);
+
+ $assertion = new \core_badges_assertion($hash, OPEN_BADGES_V2);
+ $data['assertion'] = $assertion->get_badge_assertion();
+ $response = $this->curl_request('post.assertions', $data);
+ if ($response && isset($response->status->statusCode) && $response->status->statusCode == 200) {
+ $msg['status'] = \core\output\notification::NOTIFY_SUCCESS;
+ $msg['message'] = get_string('addedtobackpack', 'badges');
+ } else {
+ $msg['status'] = \core\output\notification::NOTIFY_ERROR;
+ $msg['message'] = get_string('backpackexporterror', 'badges', $data['assertion']['badge']['name']);
+ }
+ return $msg;
+ }
+}
--- /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/>.
+
+/**
+ * Represent the url for each method and the encoding of the parameters and response.
+ *
+ * The code is based on badges/classes/backpack_api_mapping.php by Yuliya Bozhko <yuliya.bozhko@totaralms.com>.
+ *
+ * @copyright 2020 Tung Thai
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
+ */
+
+namespace core_badges;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/filelib.php');
+
+use context_system;
+use curl;
+
+/**
+ * Represent a single method for the remote api and this class using for Open Badge API v2.1 methods.
+ *
+ * @package core_badges
+ * @copyright 2020 Tung Thai
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class backpack_api2p1_mapping {
+
+ /** @var string The action of this method. */
+ public $action;
+
+ /** @var string The base url of this backpack. */
+ private $url;
+
+ /** @var array List of parameters for this method. */
+ public $params;
+
+ /** @var boolean This method returns an array of responses. */
+ public $multiple;
+
+ /** @var string get or post methods. */
+ public $method;
+
+ /** @var boolean json decode the response. */
+ public $json;
+
+ /** @var boolean Authentication is required for this request. */
+ public $authrequired;
+
+ /** @var boolean Differentiate the function that can be called on a user backpack or a site backpack. */
+ private $isuserbackpack;
+
+ /**
+ * Create a mapping.
+ *
+ * @param string $action The action of this method.
+ * @param string $url The base url of this backpack.
+ * @param mixed $postparams List of parameters for this method.
+ * @param boolean $multiple This method returns an array of responses.
+ * @param string $method get or post methods.
+ * @param boolean $json json decode the response.
+ * @param boolean $authrequired Authentication is required for this request.
+ * @param boolean $isuserbackpack user backpack or a site backpack.
+ * @param integer $backpackapiversion OpenBadges version 1 or 2.
+ */
+ public function __construct($action, $url, $postparams,
+ $multiple, $method, $json, $authrequired, $isuserbackpack, $backpackapiversion) {
+ $this->action = $action;
+ $this->url = $url;
+ $this->postparams = $postparams;
+ $this->multiple = $multiple;
+ $this->method = $method;
+ $this->json = $json;
+ $this->authrequired = $authrequired;
+ $this->isuserbackpack = $isuserbackpack;
+ $this->backpackapiversion = $backpackapiversion;
+ }
+
+ /**
+ * Does the action match this mapping?
+ *
+ * @param string $action The action.
+ * @return boolean
+ */
+ public function is_match($action) {
+ return $this->action == $action;
+ }
+
+ /**
+ * Parse the method url and insert parameters.
+ *
+ * @param string $apiurl The raw apiurl.
+ * @return string
+ */
+ private function get_url($apiurl) {
+ $urlscheme = parse_url($apiurl, PHP_URL_SCHEME);
+ $urlhost = parse_url($apiurl, PHP_URL_HOST);
+
+ $url = $this->url;
+ $url = str_replace('[SCHEME]', $urlscheme, $url);
+ $url = str_replace('[HOST]', $urlhost, $url);
+ $url = str_replace('[URL]', $apiurl, $url);
+
+ return $url;
+ }
+
+ /**
+ * Standard options used for all curl requests.
+ *
+ * @return array
+ */
+ private function get_curl_options() {
+ return array(
+ 'FRESH_CONNECT' => true,
+ 'RETURNTRANSFER' => true,
+ 'FORBID_REUSE' => true,
+ 'HEADER' => 0,
+ 'CONNECTTIMEOUT' => 3,
+ 'CONNECTTIMEOUT' => 3,
+ // Follow redirects with the same type of request when sent 301, or 302 redirects.
+ 'CURLOPT_POSTREDIR' => 3,
+ );
+ }
+
+ /**
+ * Make an api request and parse the response.
+ *
+ * @param string $apiurl Raw request url.
+ * @param string $tokenkey to verify authorization.
+ * @param array $post request method.
+ * @return bool|mixed
+ */
+ public function request($apiurl, $tokenkey, $post = []) {
+ $curl = new curl();
+ $url = $this->get_url($apiurl);
+ if ($tokenkey) {
+ $curl->setHeader('Authorization: Bearer ' . $tokenkey);
+ }
+
+ if ($this->json) {
+ $curl->setHeader(array('Content-type: application/json'));
+ if ($this->method == 'post') {
+ $post = json_encode($post);
+ }
+ }
+
+ $curl->setHeader(array('Accept: application/json', 'Expect:'));
+ $options = $this->get_curl_options();
+ if ($this->method == 'get') {
+ $response = $curl->get($url, $post, $options);
+ } else if ($this->method == 'post') {
+ $response = $curl->post($url, $post, $options);
+ }
+ $response = json_decode($response);
+ if (isset($response->result)) {
+ $response = $response->result;
+ }
+
+ return $response;
+ }
+}
\ No newline at end of file
$status = html_writer::tag('span', get_string('notconnected', 'badges'),
array('class' => 'notconnected', 'id' => 'connection-status'));
$mform->addElement('static', 'status', get_string('status'), $status);
- $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);
+ 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);
+ }
}
$this->add_action_buttons(false, get_string('backpackconnectionconnect', 'badges'));
}
* Validates form data
*/
public function validation($data, $files) {
+ global $CFG;
+
$errors = parent::validation($data, $files);
+ if (badges_open_badges_backpack_api() == OPEN_BADGES_V2P1) {
+ return $errors;
+ }
// We don't need to verify the email address if we're clearing a pending email verification attempt.
if (!isset($data['revertbutton'])) {
$check = new stdClass();
if (isset($this->_customdata['externalbackpack'])) {
$backpack = $this->_customdata['externalbackpack'];
- } else {
- throw new \coding_exception('backpack is required.');
}
- $url = $backpack->backpackapiurl;
+ $mform->addElement('hidden', 'action', 'edit');
+ $mform->setType('action', PARAM_ALPHA);
- $mform->addElement('static', 'backpackapiurlinfo', get_string('backpackapiurl', 'core_badges'), $url);
+ if ($backpack) {
+ $mform->addElement('hidden', 'id', $backpack->id);
+ $mform->setType('id', PARAM_INTEGER);
+ }
- $mform->addElement('hidden', 'backpackapiurl', $url);
+ $mform->addElement('text', 'backpackapiurl', get_string('backpackapiurl', 'core_badges'));
$mform->setType('backpackapiurl', PARAM_URL);
+ $mform->addRule('backpackapiurl', null, 'required', null, 'client');
+ $mform->addRule('backpackapiurl', get_string('maximumchars', '', 255), 'maxlength', 50, 'client');
- $url = $backpack->backpackweburl;
- $mform->addElement('static', 'backpackweburlinfo', get_string('backpackweburl', 'core_badges'), $url);
- $mform->addElement('hidden', 'backpackweburl', $url);
+ $mform->addElement('text', 'backpackweburl', get_string('backpackweburl', 'core_badges'));
$mform->setType('backpackweburl', PARAM_URL);
+ $mform->addRule('backpackweburl', null, 'required', null, 'client');
+ $mform->addRule('backpackweburl', get_string('maximumchars', '', 255), 'maxlength', 50, 'client');
- $options = badges_get_badge_api_versions();
- $label = $options[$backpack->apiversion];
- $mform->addElement('static', 'apiversioninfo', get_string('apiversion', 'core_badges'), $label);
- $mform->addElement('hidden', 'apiversion', $backpack->apiversion);
- $mform->setType('apiversion', PARAM_INTEGER);
-
- $mform->addElement('hidden', 'id', $backpack->id);
- $mform->setType('id', PARAM_INTEGER);
-
- $mform->addElement('hidden', 'action', 'edit');
- $mform->setType('action', PARAM_ALPHA);
+ $apiversions = badges_get_badge_api_versions();
+ $mform->addElement('select', 'apiversion', get_string('apiversion', 'core_badges'), $apiversions);
+ $mform->setType('apiversion', PARAM_RAW);
+ $mform->setDefault('apiversion', OPEN_BADGES_V2P1);
+ $mform->addRule('apiversion', null, 'required', null, 'client');
$issuername = $CFG->badges_defaultissuername;
$mform->addElement('static', 'issuerinfo', get_string('defaultissuername', 'core_badges'), $issuername);
$issuercontact = $CFG->badges_defaultissuercontact;
$mform->addElement('static', 'issuerinfo', get_string('defaultissuercontact', 'core_badges'), $issuercontact);
- $mform->addElement('passwordunmask', 'password', get_string('defaultissuerpassword', 'core_badges'));
- $mform->setType('password', PARAM_RAW);
- $mform->addHelpButton('password', 'defaultissuerpassword', 'badges');
- $mform->hideIf('password', 'apiversion', 'eq', 1);
-
- $this->set_data($backpack);
+ if ($backpack && $backpack->apiversion != OPEN_BADGES_V2P1) {
+ $mform->addElement('passwordunmask', 'password', get_string('defaultissuerpassword', 'core_badges'));
+ $mform->setType('password', PARAM_RAW);
+ $mform->addHelpButton('password', 'defaultissuerpassword', 'badges');
+ $mform->hideIf('password', 'apiversion', 'eq', 1);
+ } else {
+ $oauth2options = badges_get_oauth2_service_options();
+ $mform->addElement('select', 'oauth2_issuerid', get_string('oauth2issuer', 'core_badges'), $oauth2options);
+ $mform->setType('oauth2_issuerid', PARAM_INT);
+ }
+ if ($backpack) {
+ $this->set_data($backpack);
+ }
// Disable short forms.
$mform->setDisableShortforms();
$this->add_action_buttons();
}
+ /**
+ * Validate the data from the form.
+ *
+ * @param array $data form data
+ * @param array $files form files
+ * @return array An array of error messages.
+ */
+ public function validation($data, $files) {
+ $errors = parent::validation($data, $files);
+
+ // Ensure backpackapiurl and are valid URLs.
+ if (!empty($data['backpackapiurl']) && !preg_match('@^https?://.+@', $data['backpackapiurl'])) {
+ $errors['backpackapiurl'] = get_string('invalidurl', 'badges');
+ }
+ if (!empty($data['backpackweburl']) && !preg_match('@^https?://.+@', $data['backpackweburl'])) {
+ $errors['backpackweburl'] = get_string('invalidurl', 'badges');
+ }
+
+ return $errors;
+ }
}
--- /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 helper library.
+ *
+ * @package core
+ * @subpackage badges
+ * @copyright 2020 Sara Arjona <sara@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core_badges;
+
+/**
+ * Badge helper library.
+ *
+ * @copyright 2020 Sara Arjona <sara@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class helper {
+
+ /**
+ * Create a backpack.
+ *
+ * @param array $params Parameters.
+ * @return object
+ */
+ public static function create_fake_backpack(array $params = []) {
+ global $DB;
+
+ $record = (object) array_merge([
+ 'userid' => null,
+ 'email' => 'test@example.com',
+ 'backpackuid' => -1,
+ 'autosync' => 0,
+ 'password' => '',
+ 'externalbackpackid' => 12345,
+ ], $params);
+ $record->id = $DB->insert_record('badge_backpack', $record);
+
+ return $record;
+ }
+
+ /**
+ * Create a user backpack collection.
+ *
+ * @param array $params Parameters.
+ * @return object
+ */
+ public static function create_fake_backpack_collection(array $params = []) {
+ global $DB;
+
+ $record = (object) array_merge([
+ 'backpackid' => 12345,
+ 'collectionid' => -1,
+ 'entityid' => random_string(20),
+ ], $params);
+ $record->id = $DB->insert_record('badge_external', $record);
+
+ return $record;
+ }
+}
--- /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/>.
+
+/**
+ * This file to proccess Oauth2 connects for backpack.
+ *
+ * @package core_badges
+ * @subpackage badges
+ * @copyright 2020 Tung Thai
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
+ */
+
+namespace core_badges\oauth2;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir.'/authlib.php');
+
+use stdClass;
+
+/**
+ * Proccess Oauth2 connects to backpack site.
+ *
+ * @copyright 2020 Tung Thai
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
+ */
+class auth extends \auth_oauth2\auth {
+
+ /**
+ * To complete data after login.
+ *
+ * @param client $client object.
+ * @param string $redirecturl the url redirect.
+ */
+ public function complete_data(\core_badges\oauth2\client $client, $redirecturl) {
+ global $DB, $USER;
+
+ $userinfo = $client->get_userinfo();
+ $badgebackpack = new stdClass();
+ $badgebackpack->userid = $USER->id;
+ if ($userinfo && isset($userinfo->email)) {
+ $badgebackpack->email = $userinfo->email;
+ } else {
+ $badgebackpack->email = $USER->email;
+ }
+ $badgebackpack->externalbackpackid = $client->backpack->id;
+ $badgebackpack->backpackuid = 0;
+ $badgebackpack->autosync = 0;
+ $badgebackpack->password = '';
+ $record = $DB->get_record('badge_backpack', ['userid' => $USER->id,
+ 'externalbackpackid' => $client->backpack->id]);
+ if (!$record) {
+ $DB->insert_record('badge_backpack', $badgebackpack);
+ } else {
+ $badgebackpack->id = $record->id;
+ $DB->update_record('badge_backpack', $badgebackpack);
+ }
+
+ redirect($redirecturl, get_string('backpackconnected', 'badges'), null,
+ \core\output\notification::NOTIFY_SUCCESS);
+ }
+
+ /**
+ * Check user has been logged the backpack site.
+ *
+ * @param int $externalbackpackid ID of external backpack.
+ * @param int $userid ID of user.
+ * @return bool
+ */
+ public static function is_logged_oauth2($externalbackpackid, $userid) {
+ global $USER;
+ if (empty($userid)) {
+ $userid = $USER->id;
+ }
+ $persistedtoken = badge_backpack_oauth2::get_record(['externalbackpackid' => $externalbackpackid, 'userid' => $userid]);
+ if ($persistedtoken) {
+ return true;
+ }
+ return false;
+ }
+}
--- /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/>.
+
+/**
+ * This file contains the form add/update oauth2 for backpack is connected.
+ *
+ * @package core_badges
+ * @subpackage badges
+ * @copyright 2020 Tung Thai
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
+ */
+
+namespace core_badges\oauth2;
+
+defined('MOODLE_INTERNAL') || die();
+
+use core\persistent;
+
+/**
+ * Class badge_backpack_oauth2 for backpack is connected.
+ *
+ * @copyright 2020 Tung Thai
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
+ */
+class badge_backpack_oauth2 extends persistent {
+
+ /**
+ * The table name.
+ */
+ const TABLE = 'badge_backpack_oauth2';
+
+ /**
+ * Return the definition of the properties of this model.
+ *
+ * @return array
+ */
+ protected static function define_properties() {
+ return array(
+ 'userid' => array(
+ 'type' => PARAM_INT,
+ ),
+ 'issuerid' => array(
+ 'type' => PARAM_INT
+ ),
+ 'externalbackpackid' => array(
+ 'type' => PARAM_INT
+ ),
+ 'token' => array(
+ 'type' => PARAM_TEXT
+ ),
+ 'refreshtoken' => array(
+ 'type' => PARAM_TEXT
+ ),
+ 'expires' => array(
+ 'type' => PARAM_INT
+ ),
+ 'scope' => array(
+ 'type' => PARAM_TEXT
+ ),
+ );
+ }
+}
\ No newline at end of file
--- /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/>.
+
+/**
+ * Configurable OAuth2 client class.
+ *
+ * @package core_badges
+ * @subpackage badges
+ * @copyright 2020 Tung Thai
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
+ */
+
+namespace core_badges\oauth2;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/oauthlib.php');
+require_once($CFG->libdir . '/filelib.php');
+require_once('badge_backpack_oauth2.php');
+
+use moodle_url;
+use moodle_exception;
+use stdClass;
+
+define('BACKPACK_CHALLENGE_METHOD', 'S256');
+define('BACKPACK_CODE_VERIFIER_TIME', 60);
+
+/**
+ * Configurable OAuth2 client to request authorization and store token. Use the PKCE method to verifier authorization.
+ *
+ * @copyright 2020 Tung Thai
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
+ */
+class client extends \core\oauth2\client {
+
+ /** @var \core\oauth2\issuer */
+ private $issuer;
+
+ /** @var string $clientid client identifier issued to the client */
+ private $clientid = '';
+
+ /** @var string $clientsecret The client secret. */
+ private $clientsecret = '';
+
+ /** @var moodle_url $returnurl URL to return to after authenticating */
+ private $returnurl = null;
+
+ /** @var string $grantscope */
+ protected $grantscope = '';
+
+ /** @var string $scope */
+ protected $scope = '';
+
+ /** @var bool basicauth */
+ protected $basicauth = true;
+
+ /** @var string|null backpack object */
+ public $backpack = '';
+
+ /**
+ * client constructor.
+ *
+ * @param issuer $issuer oauth2 service.
+ * @param string $returnurl return url after login
+ * @param string $additionalscopes the scopes has been granted
+ * @param null $backpack backpack object.
+ * @throws \coding_exception error message.
+ */
+ public function __construct(\core\oauth2\issuer $issuer, $returnurl = '', $additionalscopes = '',
+ $backpack = null) {
+ $this->issuer = $issuer;
+ $this->clientid = $issuer->get('clientid');
+ $this->returnurl = $returnurl;
+ $this->clientsecret = $issuer->get('clientsecret');
+ $this->backpack = $backpack;
+ $this->grantscope = $additionalscopes;
+ $this->scope = $additionalscopes;
+ parent::__construct($issuer, $returnurl, $additionalscopes, false);
+ }
+
+ /**
+ * Get login url.
+ *
+ * @return moodle_url
+ * @throws \coding_exception
+ * @throws moodle_exception
+ */
+ public function get_login_url() {
+ $callbackurl = self::callback_url();
+ $scopes = $this->issuer->get('scopessupported');
+
+ // Removed the scopes does not support in authorization.
+ $excludescopes = ['profile', 'openid'];
+ $arrascopes = explode(' ', $scopes);
+ foreach ($excludescopes as $exscope) {
+ $key = array_search($exscope, $arrascopes);
+ if (isset($key)) {
+ unset($arrascopes[$key]);
+ }
+ }
+ $scopes = implode(' ', $arrascopes);
+
+ $params = array_merge(
+ [
+ 'client_id' => $this->clientid,
+ 'response_type' => 'code',
+ 'redirect_uri' => $callbackurl->out(false),
+ 'state' => $this->returnurl->out_as_local_url(false),
+ 'scope' => $scopes,
+ 'code_challenge' => $this->code_challenge(),
+ 'code_challenge_method' => BACKPACK_CHALLENGE_METHOD,
+ ]
+ );
+ return new moodle_url($this->auth_url(), $params);
+ }
+
+ /**
+ * Generate code challenge.
+ *
+ * @return string
+ */
+ public function code_challenge() {
+ $random = bin2hex(openssl_random_pseudo_bytes(43));
+ $verifier = $this->base64url_encode(pack('H*', $random));
+ $challenge = $this->base64url_encode(pack('H*', hash('sha256', $verifier)));
+ $_SESSION['SESSION']->code_verifier = $verifier;
+ return $challenge;
+ }
+
+ /**
+ * Get code verifier.
+ *
+ * @return bool
+ */
+ public function code_verifier() {
+ if (isset($_SESSION['SESSION']) && !empty($_SESSION['SESSION']->code_verifier)) {
+ return $_SESSION['SESSION']->code_verifier;
+ }
+ return false;
+ }
+
+ /**
+ * Generate base64url encode.
+ *
+ * @param string $plaintext text to convert.
+ * @return string
+ */
+ public function base64url_encode($plaintext) {
+ $base64 = base64_encode($plaintext);
+ $base64 = trim($base64, "=");
+ $base64url = strtr($base64, '+/', '-_');
+ return ($base64url);
+ }
+
+ /**
+ * Callback url where the request is returned to.
+ *
+ * @return moodle_url url of callback
+ */
+ public static function callback_url() {
+ return new moodle_url('/badges/oauth2callback.php');
+ }
+
+ /**
+ * Check and refresh token to keep login on backpack site.
+ *
+ * @return bool
+ * @throws \coding_exception
+ * @throws moodle_exception
+ */
+ public function is_logged_in() {
+
+ // Has the token expired?
+ if (isset($this->accesstoken->expires) && time() >= $this->accesstoken->expires) {
+ if (isset($this->accesstoken->refreshtoken)) {
+ return $this->upgrade_token($this->accesstoken->refreshtoken, 'refresh_token');
+ } else {
+ throw new moodle_exception('Could not refresh oauth token, please try again.');
+ }
+ }
+
+ if (isset($this->accesstoken->token) && isset($this->accesstoken->scope)) {
+ return true;
+ }
+
+ // If we've been passed then authorization code generated by the
+ // authorization server try and upgrade the token to an access token.
+ $code = optional_param('oauth2code', null, PARAM_RAW);
+ // Note - sometimes we may call is_logged_in twice in the same request - we don't want to attempt
+ // to upgrade the same token twice.
+ if ($code && $this->upgrade_token($code, 'authorization_code')) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Request new token.
+ *
+ * @param string $code code verify from Auth site.
+ * @param string $granttype grant type.
+ * @return bool
+ * @throws moodle_exception
+ */
+ public function upgrade_token($code, $granttype = 'authorization_code') {
+ $callbackurl = self::callback_url();
+
+ if ($granttype == 'authorization_code') {
+ $params = array('code' => $code,
+ 'grant_type' => $granttype,
+ 'redirect_uri' => $callbackurl->out(false),
+ 'scope' => $this->get_scopes(),
+ 'code_verifier' => $this->code_verifier()
+ );
+ } else if ($granttype == 'refresh_token') {
+ $this->basicauth = false;
+ $params = array('refresh_token' => $code,
+ 'grant_type' => $granttype,
+ 'scope' => $this->get_scopes(),
+ );
+ }
+ if ($this->basicauth) {
+ $idsecret = urlencode($this->clientid) . ':' . urlencode($this->clientsecret);
+ $this->setHeader('Authorization: Basic ' . base64_encode($idsecret));
+ } else {
+ $params['client_id'] = $this->clientid;
+ $params['client_secret'] = $this->clientsecret;
+ }
+ // Requests can either use http GET or POST.
+ $response = $this->post($this->token_url(), $this->build_post_data($params));
+ $r = json_decode($response);
+ if ($this->info['http_code'] !== 200) {
+ throw new moodle_exception('Could not upgrade oauth token');
+ }
+
+ if (is_null($r)) {
+ throw new moodle_exception("Could not decode JSON token response");
+ }
+
+ if (!empty($r->error)) {
+ throw new moodle_exception($r->error . ' ' . $r->error_description);
+ }
+
+ if (!isset($r->access_token)) {
+ return false;
+ }
+
+ // Store the token an expiry time.
+ $accesstoken = new stdClass;
+ $accesstoken->token = $r->access_token;
+ if (isset($r->expires_in)) {
+ // Expires 10 seconds before actual expiry.
+ $accesstoken->expires = (time() + ($r->expires_in - 10));
+ }
+ if (isset($r->refresh_token)) {
+ $this->refreshtoken = $r->refresh_token;
+ $accesstoken->refreshtoken = $r->refresh_token;
+ }
+ $accesstoken->scope = $r->scope;
+
+ // Also add the scopes.
+ $this->store_token($accesstoken);
+
+ return true;
+ }
+
+ /**
+ * Store a token to verify for send request.
+ *
+ * @param null|stdClass $token
+ */
+ protected function store_token($token) {
+ global $USER;
+
+ $this->accesstoken = $token;
+ // Create or update a DB record with the new token.
+ $persistedtoken = badge_backpack_oauth2::get_record(['externalbackpackid' => $this->backpack->id, 'userid' => $USER->id]);
+ if ($token !== null) {
+ if (!$persistedtoken) {
+ $persistedtoken = new badge_backpack_oauth2();
+ $persistedtoken->set('issuerid', $this->backpack->oauth2_issuerid);
+ $persistedtoken->set('externalbackpackid', $this->backpack->id);
+ $persistedtoken->set('userid', $USER->id);
+ } else {
+ $persistedtoken->set('timemodified', time());
+ }
+ // Update values from $token. Don't use from_record because that would skip validation.
+ $persistedtoken->set('usermodified', $USER->id);
+ $persistedtoken->set('token', $token->token);
+ $persistedtoken->set('refreshtoken', $token->refreshtoken);
+ $persistedtoken->set('expires', $token->expires);
+ $persistedtoken->set('scope', $token->scope);
+ $persistedtoken->save();
+ } else {
+ if ($persistedtoken) {
+ $persistedtoken->delete();
+ }
+ }
+ }
+
+ /**
+ * Get token of current user.
+ *
+ * @return stdClass|null token object
+ */
+ protected function get_stored_token() {
+ global $USER;
+
+ $token = badge_backpack_oauth2::get_record(['externalbackpackid' => $this->backpack->id, 'userid' => $USER->id]);
+ if ($token !== false) {
+ $token = $token->to_record();
+ return $token;
+ }
+ return null;
+ }
+
+ /**
+ * Get scopes granted.
+ *
+ * @return null|string
+ */
+ protected function get_scopes() {
+ if (!empty($this->grantscope)) {
+ return $this->grantscope;
+ }
+ $token = $this->get_stored_token();
+ if ($token) {
+ return $token->scope;
+ }
+ return null;
+ }
+}
* @return stdClass
*/
public function export_for_template(\renderer_base $output) {
+ global $CFG, $PAGE;
+
+ $PAGE->requires->js_call_amd('core_badges/backpackactions', 'init');
+
$data = new \stdClass();
$data->baseurl = $this->url;
$data->backpacks = array();
foreach ($this->backpacks as $backpack) {
$exporter = new backpack_exporter($backpack);
$backpack = $exporter->export($output);
- if ($backpack->apiversion == OPEN_BADGES_V2) {
+ if ($backpack->apiversion == OPEN_BADGES_V2 || $backpack->apiversion == OPEN_BADGES_V2P1) {
$backpack->canedit = true;
} else {
$backpack->canedit = false;
}
+ $backpack->iscurrent = ($backpack->id == $CFG->badges_site_backpack);
+
$data->backpacks[] = $backpack;
}
$data->warning = badges_verify_site_backpack();
'issuer' => 'privacy:metadata:external:backpacks:issuer',
], 'privacy:metadata:external:backpacks');
+ $collection->add_database_table('badge_backpack_oauth2', [
+ 'userid' => 'privacy:metadata:backpackoauth2:userid',
+ 'usermodified' => 'privacy:metadata:backpackoauth2:usermodified',
+ 'token' => 'privacy:metadata:backpackoauth2:token',
+ 'issuerid' => 'privacy:metadata:backpackoauth2:issuerid',
+ 'scope' => 'privacy:metadata:backpackoauth2:scope',
+ ], 'privacy:metadata:backpackoauth2');
+
return $collection;
}
if ($disconnect && $backpack) {
require_sesskey();
$sitebackpack = badges_get_site_backpack($backpack->externalbackpackid);
- // If backpack is connected, need to select collections.
- $bp = new \core_badges\backpack_api($sitebackpack, $backpack);
- $bp->disconnect_backpack($USER->id, $backpack->id);
- redirect(new moodle_url('/badges/mybackpack.php'));
+ if ($sitebackpack->apiversion == OPEN_BADGES_V2P1) {
+ $bp = new \core_badges\backpack_api2p1($sitebackpack);
+ $bp->disconnect_backpack($backpack);
+ redirect(new moodle_url('/badges/mybackpack.php'), get_string('backpackdisconnected', 'badges'), null,
+ \core\output\notification::NOTIFY_SUCCESS);
+ } else {
+ // If backpack is connected, need to select collections.
+ $bp = new \core_badges\backpack_api($sitebackpack, $backpack);
+ $bp->disconnect_backpack($USER->id, $backpack->id);
+ redirect(new moodle_url('/badges/mybackpack.php'));
+ }
}
$warning = '';
if ($backpack) {
$bp->set_backpack_collections($backpack->id, $groups);
redirect(new moodle_url('/badges/mybadges.php'));
}
+} else if (badges_open_badges_backpack_api() == OPEN_BADGES_V2P1) {
+ // If backpack is version 2.1 to redirect on the backpack site to login.
+ // User input username/email/password on the backpack site
+ // After confirm the scopes.
+ $form = new \core_badges\form\backpack(new moodle_url('/badges/mybackpack.php'));
+ if ($form->is_cancelled()) {
+ redirect(new moodle_url('/badges/mybadges.php'));
+ } else if ($data = $form->get_submitted_data()) {
+ redirect(new moodle_url('/badges/backpack-connect.php'));
+ }
} else {
// If backpack is not connected, need to connect first.
// To create a new connection to the backpack, first we need to verify the user's email address:
--- /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/>.
+
+/**
+ * Verify authorization callback.
+ *
+ * @package core_badges
+ * @copyright 2020 Tung Thai
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @author Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
+ */
+require_once(__DIR__ . '/../config.php');
+
+$error = optional_param('error', '', PARAM_RAW);
+
+if ($error) {
+ $message = optional_param('error_description', '', PARAM_RAW);
+ if ($message) {
+ print_error($message);
+ } else {
+ print_error($error);
+ }
+ die();
+}
+
+require_login();
+
+// The authorization code generated by the authorization server.
+$code = required_param('code', PARAM_RAW);
+$scope = required_param('scope', PARAM_RAW);
+
+// The state parameter we've given (used in moodle as a redirect url).
+$state = required_param('state', PARAM_LOCALURL);
+
+$redirecturl = new moodle_url($state);
+$params = $redirecturl->params();
+
+if (isset($params['sesskey']) and confirm_sesskey($params['sesskey'])) {
+ $redirecturl->param('oauth2code', $code);
+ $redirecturl->param('scope', $scope);
+ redirect($redirecturl);
+} else {
+ print_error('invalidsesskey');
+}
if (badges_open_badges_backpack_api() == OPEN_BADGES_V1) {
$action = new component_action('click', 'addtobackpack', array('assertion' => $assertion->out(false)));
$addurl = new moodle_url('#');
+ } else if (badges_open_badges_backpack_api() == OPEN_BADGES_V2P1) {
+ $addurl = new moodle_url('/badges/backpack-export.php', array('hash' => $badge->uniquehash));
} else {
$addurl = new moodle_url('/badges/backpack-add.php', array('hash' => $badge->uniquehash));
}
$this->output->add_action_handler($action, 'addbutton');
$output .= $tobackpack;
} else {
- $assertion = new moodle_url('/badges/backpack-add.php', array('hash' => $ibadge->hash));
+ if (badges_open_badges_backpack_api() == OPEN_BADGES_V2P1) {
+ $assertion = new moodle_url('/badges/backpack-export.php', array('hash' => $ibadge->hash));
+ } else {
+ $assertion = new moodle_url('/badges/backpack-add.php', array('hash' => $ibadge->hash));
+ }
$attributes = ['class' => 'btn btn-secondary m-1', 'role' => 'button'];
$tobackpack = html_writer::link($assertion, get_string('addtobackpack', 'badges'), $attributes);
$output .= $tobackpack;
"warning": "<span class='text-warning'>Could not login</span>"
}
}}
-<table class="generaltable fullwidth">
+
+<form action="{{baseurl}}" method="get" id="createbackpack">
+ <input type="hidden" name="action" value="edit"/>
+ <button type="submit" class="btn btn-secondary">{{#str}}newbackpack, core_badges{{/str}}</button>
+</form>
+
+<table class="generaltable fullwidth" id="backpacklist">
<caption>{{#str}}listbackpacks, core_badges{{/str}}</caption>
<thead>
<tr>
</thead>
<tbody>
{{#backpacks}}
- <tr>
+ <tr data-backpackurl="{{{backpackweburl}}}">
<td> {{{backpackweburl}}} </td>
<td> {{#sitebackpack}}Yes{{/sitebackpack}} </td>
<td>
{{#canedit}}
- <a href="{{baseurl}}?id={{id}}&action=edit">
- {{#str}}editsettings, core_badges{{/str}}
- </a>
+ <a href="{{baseurl}}?id={{id}}&action=edit">{{#pix}}t/edit, core,{{#str}}editsettings{{/str}}{{/pix}}</a>
{{/canedit}}
+ {{^iscurrent}}
+ <a href="{{baseurl}}?id={{id}}&action=delete" role="button" data-action="deletebackpack">
+ {{#pix}}t/delete, core,{{#str}}delete{{/str}}{{/pix}}
+ </a>
+ {{/iscurrent}}
</td>
</tr>
{{/backpacks}}
require_once($CFG->libdir . '/badgeslib.php');
require_once($CFG->dirroot . '/badges/lib.php');
+use core_badges\helper;
+
class core_badges_badgeslib_testcase extends advanced_testcase {
protected $badgeid;
protected $course;
$badge->delete_alignment($alignments1[$newid2]->id);
$this->assertCount(1, $badge->get_alignments());
}
+
+ /**
+ * Test badges_delete_site_backpack().
+ *
+ */
+ public function test_badges_delete_site_backpack(): void {
+ global $DB;
+
+ $this->setAdminUser();
+
+ // Create one backpack.
+ $total = $DB->count_records('badge_external_backpack');
+ $this->assertEquals(1, $total);
+
+ $data = new \stdClass();
+ $data->apiversion = OPEN_BADGES_V2P1;
+ $data->backpackapiurl = 'https://dc.imsglobal.org/obchost/ims/ob/v2p1';
+ $data->backpackweburl = 'https://dc.imsglobal.org';
+ badges_create_site_backpack($data);
+ $backpack = $DB->get_record('badge_external_backpack', ['backpackweburl' => $data->backpackweburl]);
+ $user1 = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+ // User1 is connected to the backpack to be removed and has 2 collections.
+ $backpackuser1 = helper::create_fake_backpack(['userid' => $user1->id, 'externalbackpackid' => $backpack->id]);
+ helper::create_fake_backpack_collection(['backpackid' => $backpackuser1->id]);
+ helper::create_fake_backpack_collection(['backpackid' => $backpackuser1->id]);
+ // User2 is connected to a different backpack and has 1 collection.
+ $backpackuser2 = helper::create_fake_backpack(['userid' => $user2->id]);
+ helper::create_fake_backpack_collection(['backpackid' => $backpackuser2->id]);
+
+ $total = $DB->count_records('badge_external_backpack');
+ $this->assertEquals(2, $total);
+ $total = $DB->count_records('badge_backpack');
+ $this->assertEquals(2, $total);
+ $total = $DB->count_records('badge_external');
+ $this->assertEquals(3, $total);
+
+ // Remove the backpack created previously.
+ $result = badges_delete_site_backpack($backpack->id);
+ $this->assertTrue($result);
+
+ $total = $DB->count_records('badge_external_backpack');
+ $this->assertEquals(1, $total);
+
+ $total = $DB->count_records('badge_backpack');
+ $this->assertEquals(1, $total);
+
+ $total = $DB->count_records('badge_external');
+ $this->assertEquals(1, $total);
+
+ // Try to remove an non-existent backpack.
+ $result = badges_delete_site_backpack($backpack->id);
+ $this->assertFalse($result);
+ }
}
--- /dev/null
+@core @core_badges @_file_upload
+Feature: Backpack badges
+ The settings to connect to backpack with OAuth2 service
+ As an learner
+ I need to verify display backpack in the my profile
+
+ Background:
+ Given the following "badge external backpack" exist:
+ | backpackapiurl | backpackweburl | apiversion |
+ | https://dc.imsglobal.org/obchost/ims/ob/v2p1 | https://dc.imsglobal.org | 2.1 |
+ And the following "users" exist:
+ | username | firstname | lastname | email |
+ | student1 | Student | 1 | student1@example.com |
+
+ @javascript
+ Scenario: Verify backback settings
+ Given I am on homepage
+ And I log in as "admin"
+ And I navigate to "Badges > Backpack settings" in site administration
+ And I set the following fields to these values:
+ | External backpack connection | 1 |
+ | Active external backpack | https://dc.imsglobal.org |
+ And I press "Save changes"
+ And I navigate to "Badges > Add a new badge" in site administration
+ And I set the following fields to these values:
+ | Name | Test badge verify backpack |
+ | Version | v1 |
+ | Language | English |
+ | Description | Test badge description |
+ | Image author | http://author.example.com |
+ | Image caption | Test caption image |
+ | issuername | Test Badge Site |
+ | issuercontact | testuser@example.com |
+ 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 "Manager" 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 1 (student1@example.com)"
+ And I press "Award badge"
+ And I log out
+ When I am on homepage
+ And I log in as "student1"
+ And I follow "Preferences" in the user menu
+ And I follow "Backpack settings"
+ Then I should see "https://dc.imsglobal.org"
+ And I should see "Not connected"
+
+ @javascript
+ Scenario: User has been connected backpack
+ Given I am on homepage
+ And I log in as "admin"
+ And I navigate to "Badges > Backpack settings" in site administration
+ And I set the following fields to these values:
+ | External backpack connection | 1 |
+ | Active external backpack | https://dc.imsglobal.org |
+ And I press "Save changes"
+ And I navigate to "Badges > Add a new badge" in site administration
+ And I set the following fields to these values:
+ | Name | Test badge verify backpack |
+ | Version | v1 |
+ | Language | English |
+ | Description | Test badge description |
+ | Image author | http://author.example.com |
+ | Image caption | Test caption image |
+ | issuername | Test Badge Site |
+ | issuercontact | testuser@example.com |
+ 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 "Manager" 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 1 (student1@example.com)"
+ And I press "Award badge"
+ And I log out
+ And the following "setup backpack connected" exist:
+ | user | externalbackpack |
+ | student1 | https://dc.imsglobal.org |
+ When I log in as "student1"
+ And I follow "Preferences" in the user menu
+ And I follow "Backpack settings"
+ Then I should see "Connected"
+ And I follow "Preferences" in the user menu
+ And I follow "Manage badges"
+ And I should see "Test badge verify backpack"
+ And "Add to backpack" "link" should exist
+
+ @javascript
+ Scenario: Add a new site backpack
+ Given I am on homepage
+ And I log in as "admin"
+ And I navigate to "Badges > Manage backpacks" in site administration
+ When I press "Add a new backpack"
+ And I set the field "backpackapiurl" to "http://backpackapiurl.cat"
+ And I set the field "backpackweburl" to "aaa"
+ And I press "Save changes"
+ And I should see "Invalid URL"
+ And I set the field "backpackweburl" to "http://backpackweburl.cat"
+ And I press "Save changes"
+ Then I should see "http://backpackweburl.cat"
+ And "Delete" "button" should exist
+
+ @javascript
+ Scenario: Remove a site backpack
+ Given I am on homepage
+ And I log in as "admin"
+ And I navigate to "Badges > Manage backpacks" in site administration
+ When I click on "Delete" "link" in the "https://dc.imsglobal.org" "table_row"
+ And I should see "Delete site backpack 'https://dc.imsglobal.org'?"
+ And I click on "Delete" "button" in the "Delete site backpack" "dialogue"
+ Then I should see "The site backpack has been deleted."
+ And I should not see "https://dc.imsglobal.org"
+ And "Delete" "button" should not exist