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
--- /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"
--- /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]',
+ },
+};
$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);
}
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);
+ $apiversions = badges_get_badge_api_versions();
+ $mform->addElement('select', 'apiversion', get_string('apiversion', 'core_badges'), $apiversions);
$mform->setType('apiversion', PARAM_RAW);
-
- $mform->addElement('hidden', 'id', $backpack->id);
- $mform->setType('id', PARAM_INTEGER);
-
- $mform->addElement('hidden', 'action', 'edit');
- $mform->setType('action', PARAM_ALPHA);
+ $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);
- if ($backpack->apiversion != OPEN_BADGES_V2P1) {
+
+ 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->addElement('select', 'oauth2_issuerid', get_string('oauth2issuer', 'core_badges'), $oauth2options);
$mform->setType('oauth2_issuerid', PARAM_INT);
}
- $this->set_data($backpack);
+ 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;
+ }
+}
* @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();
} else {
$backpack->canedit = false;
}
+ $backpack->iscurrent = ($backpack->id == $CFG->badges_site_backpack);
+
$data->backpacks[] = $backpack;
}
$data->warning = badges_verify_site_backpack();
"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);
+ }
}
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
use core_privacy\local\request\writer;
use core_badges\privacy\provider;
use core_privacy\local\request\approved_userlist;
+use core_badges\helper;
require_once($CFG->libdir . '/badgeslib.php');
$b1 = $this->create_badge();
$b2 = $this->create_badge(['type' => BADGE_TYPE_COURSE, 'courseid' => $c1->id]);
- $this->create_backpack(['userid' => $u1->id]);
+ helper::create_fake_backpack(['userid' => $u1->id]);
$this->create_manual_award(['recipientid' => $u2->id, 'badgeid' => $b1->id]);
$this->create_issued(['badgeid' => $b2->id, 'userid' => $u3->id]);
$b2 = $this->create_badge(['usercreated' => $u2->id, 'usermodified' => $u1->id,
'type' => BADGE_TYPE_COURSE, 'courseid' => $c1->id]);
- $this->create_backpack(['userid' => $u1->id]);
- $this->create_backpack(['userid' => $u2->id]);
+ helper::create_fake_backpack(['userid' => $u1->id]);
+ helper::create_fake_backpack(['userid' => $u2->id]);
$this->create_manual_award(['recipientid' => $u1->id, 'badgeid' => $b1->id]);
$this->create_manual_award(['recipientid' => $u2->id, 'badgeid' => $b1->id, 'issuerid' => $u1->id]);
$this->create_issued(['badgeid' => $b2->id, 'userid' => $u1->id]);
$b2 = $this->create_badge(['usercreated' => $u2->id, 'usermodified' => $u1->id,
'type' => BADGE_TYPE_COURSE, 'courseid' => $c1->id]);
- $this->create_backpack(['userid' => $u1->id]);
- $this->create_backpack(['userid' => $u2->id]);
+ helper::create_fake_backpack(['userid' => $u1->id]);
+ helper::create_fake_backpack(['userid' => $u2->id]);
$this->create_manual_award(['recipientid' => $u1->id, 'badgeid' => $b1->id]);
$this->create_manual_award(['recipientid' => $u2->id, 'badgeid' => $b1->id, 'issuerid' => $u1->id]);
$this->create_issued(['badgeid' => $b2->id, 'userid' => $u1->id]);
// Create things for user 2, to check it's not exported it.
$this->create_issued(['badgeid' => $b4->id, 'userid' => $u2->id]);
- $this->create_backpack(['userid' => $u2->id, 'email' => $u2->email]);
+ helper::create_fake_backpack(['userid' => $u2->id, 'email' => $u2->email]);
$this->create_manual_award(['badgeid' => $b1->id, 'recipientid' => $u2->id, 'issuerid' => $u3->id]);
// Create a set of stuff for u1.
$this->create_issued(['badgeid' => $b1->id, 'userid' => $u1->id, 'uniquehash' => 'yoohoo']);
$this->create_manual_award(['badgeid' => $b2->id, 'recipientid' => $u1->id, 'issuerid' => $u3->id]);
$b3crit->mark_complete($u1->id);
- $this->create_backpack(['userid' => $u1->id, 'email' => $u1->email]);
+ helper::create_fake_backpack(['userid' => $u1->id, 'email' => $u1->email]);
// Check u1.
writer::reset();
$this->create_manual_award(['recipientid' => $user3->id, 'issuerid' => $user2->id, 'badgeid' => $badge1->id]);
$this->create_manual_award(['recipientid' => $user1->id, 'issuerid' => $user2->id, 'badgeid' => $badge2->id]);
- $this->create_backpack(['userid' => $user2->id]);
+ helper::create_fake_backpack(['userid' => $user2->id]);
$this->create_issued(['badgeid' => $badge2->id, 'userid' => $user3->id]);
$crit = $this->create_criteria_manual($badge1->id);
$this->create_manual_award(['recipientid' => $user3->id, 'issuerid' => $user2->id, 'badgeid' => $badge1->id]);
$this->create_manual_award(['recipientid' => $user1->id, 'issuerid' => $user2->id, 'badgeid' => $badge2->id]);
- $this->create_backpack(['userid' => $user2->id]);
+ helper::create_fake_backpack(['userid' => $user2->id]);
$this->create_issued(['badgeid' => $badge2->id, 'userid' => $user3->id]);
$crit = $this->create_criteria_manual($badge1->id);
return $record;
}
- /**
- * Create a backpack.
- *
- * @param array $params Parameters.
- * @return object
- */
- protected function create_backpack(array $params = []) {
- global $DB;
- $record = (object) array_merge([
- 'userid' => null,
- 'email' => 'test@example.com',
- 'backpackurl' => "http://here.there.com",
- 'backpackuid' => "12345",
- 'autosync' => 0,
- 'password' => '',
- ], $params);
- $record->id = $DB->insert_record('badge_backpack', $record);
- return $record;
- }
-
/**
* Create a criteria of type badge.
*
* @throws \dml_exception
*/
public function __construct($grouping, $sort, $view, $paging, $customfieldvalue = null) {
+ global $CFG;
// Get plugin config.
$config = get_config('block_myoverview');
$this->customfieldvalue = $customfieldvalue;
// Check and remember the given sorting.
- $this->sort = $sort ? $sort : BLOCK_MYOVERVIEW_SORTING_TITLE;
+ if ($sort) {
+ $this->sort = $sort;
+ } else if ($CFG->courselistshortnames) {
+ $this->sort = BLOCK_MYOVERVIEW_SORTING_SHORTNAME;
+ } else {
+ $this->sort = BLOCK_MYOVERVIEW_SORTING_TITLE;
+ }
+ // In case sorting remembered is shortname and display extended course names not checked,
+ // we should revert sorting to title.
+ if (!$CFG->courselistshortnames && $sort == BLOCK_MYOVERVIEW_SORTING_SHORTNAME) {
+ $this->sort = BLOCK_MYOVERVIEW_SORTING_TITLE;
+ }
// Check and remember the given view.
$this->view = $view ? $view : BLOCK_MYOVERVIEW_VIEW_CARD;
}
unset ($displaygroupingselectors, $displaygroupingselectorscount);
}
-
/**
* Determine the most sensible fallback grouping to use (in cases where the stored selection
* is no longer available).
*
*/
public function export_for_template(renderer_base $output) {
- global $USER;
+ global $CFG, $USER;
$nocoursesurl = $output->image_url('courses', 'block_myoverview')->out();
}
$preferences = $this->get_preferences_as_booleans();
$availablelayouts = $this->get_formatted_available_layouts_for_export();
+ $sort = '';
+ if ($this->sort == BLOCK_MYOVERVIEW_SORTING_SHORTNAME) {
+ $sort = 'shortname';
+ } else {
+ $sort = $this->sort == BLOCK_MYOVERVIEW_SORTING_TITLE ? 'fullname' : 'ul.timeaccess desc';
+ }
$defaultvariables = [
'totalcoursecount' => count(enrol_get_all_users_courses($USER->id, true)),
'nocoursesimg' => $nocoursesurl,
'grouping' => $this->grouping,
- 'sort' => $this->sort == BLOCK_MYOVERVIEW_SORTING_TITLE ? 'fullname' : 'ul.timeaccess desc',
+ 'sort' => $sort,
// If the user preference display option is not available, default to first available layout.
'view' => in_array($this->view, $this->layouts) ? $this->view : reset($this->layouts),
'paging' => $this->paging,
'customfieldvalue' => $this->customfieldvalue,
'customfieldvalues' => $customfieldvalues,
'selectedcustomfield' => $selectedcustomfield,
+ 'showsortbyshortname' => $CFG->courselistshortnames,
];
return array_merge($defaultvariables, $preferences);
$string['aria:title'] = 'Sort courses by course name';
$string['aria:past'] = 'Show past courses';
$string['aria:removefromfavourites'] = 'Remove star for';
+$string['aria:shortname'] = 'Sort courses by course short name';
$string['aria:summary'] = 'Switch to summary view';
$string['aria:sortingdropdown'] = 'Sorting drop-down menu';
$string['availablegroupings'] = 'Available filters';
$string['privacy:metadata:overviewgroupingpreference'] = 'The Course overview block grouping preference.';
$string['privacy:metadata:overviewpagingpreference'] = 'The Course overview block paging preference.';
$string['removefromfavourites'] = 'Unstar this course';
+$string['shortname'] = 'Short name';
$string['summary'] = 'Summary';
$string['title'] = 'Course name';
$string['aria:hidecourse'] = 'Remove {$a} from view';
*/
define('BLOCK_MYOVERVIEW_SORTING_TITLE', 'title');
define('BLOCK_MYOVERVIEW_SORTING_LASTACCESSED', 'lastaccessed');
+define('BLOCK_MYOVERVIEW_SORTING_SHORTNAME', 'shortname');
/**
* Constants for the user preferences view options
'type' => PARAM_ALPHA,
'choices' => array(
BLOCK_MYOVERVIEW_SORTING_TITLE,
- BLOCK_MYOVERVIEW_SORTING_LASTACCESSED
+ BLOCK_MYOVERVIEW_SORTING_LASTACCESSED,
+ BLOCK_MYOVERVIEW_SORTING_SHORTNAME
)
);
$preferences['block_myoverview_user_view_preference'] = array(
<span class="d-sm-inline-block" data-active-item-text>
{{#title}}{{#str}} title, block_myoverview {{/str}}{{/title}}
{{#lastaccessed}}{{#str}} lastaccessed, block_myoverview {{/str}}{{/lastaccessed}}
+ {{#shortname}}{{#str}} shortname, block_myoverview {{/str}}{{/shortname}}
</span>
</button>
<ul class="dropdown-menu" data-show-active-item aria-labelledby="sortingdropdown">
{{#str}} title, block_myoverview {{/str}}
</a>
</li>
+ {{#showsortbyshortname}}
+ <li>
+ <a class="dropdown-item {{#shortname}}active{{/shortname}}" href="#" data-filter="sort" data-pref="shortname" data-value="shortname" aria-label="{{#str}} aria:shortname, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
+ {{#str}} shortname, block_myoverview {{/str}}
+ </a>
+ </li>
+ {{/showsortbyshortname}}
<li>
<a class="dropdown-item {{#lastaccessed}}active{{/lastaccessed}}" href="#" data-filter="sort" data-pref="lastaccessed" data-value="ul.timeaccess desc" aria-label="{{#str}} aria:lastaccessed, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
{{#str}} lastaccessed, block_myoverview {{/str}}
</li>
</ul>
</div>
-</div>
\ No newline at end of file
+</div>
<div>{{{shortname}}}</div>
{{/showshortname}}
</div>
- <a href="{{viewurl}}" class="coursename">
+ <a href="{{viewurl}}" class="aalink coursename">
{{> core_course/favouriteicon }}
<span class="sr-only">
{{#str}}aria:coursename, core_course{{/str}}
{{/showshortname}}
</div>
<div class="d-flex mb-1">
- <a href="{{viewurl}}" class="coursename">
+ <a href="{{viewurl}}" class="aalink coursename">
{{> core_course/favouriteicon }}
<span class="sr-only">
{{#str}}aria:coursename, core_course{{/str}}
Then I should see "Last accessed" in the "Course overview" "block"
And "[data-sort='ul.timeaccess desc']" "css_element" in the "Course overview" "block" should be visible
+ Scenario: Short name sort persistence
+ Given I log in as "student1"
+ When I click on "sortingdropdown" "button" in the "Course overview" "block"
+ Then I should not see "Short name" in the "Course overview" "block"
+ When the following config values are set as admin:
+ | config | value |
+ | courselistshortnames | 1 |
+ And I reload the page
+ And I click on "sortingdropdown" "button" in the "Course overview" "block"
+ And I click on "Short name" "link" in the "Course overview" "block"
+ And I reload the page
+ Then I should see "Short name" in the "Course overview" "block"
+ And "[data-sort='shortname']" "css_element" in the "Course overview" "block" should be visible
+
Scenario: View inprogress courses with hide persistent functionality
Given I log in as "student1"
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
return array(
array('block_myoverview_user_sort_preference', 'lastaccessed', ''),
array('block_myoverview_user_sort_preference', 'title', ''),
+ array('block_myoverview_user_sort_preference', 'shortname', ''),
array('block_myoverview_user_grouping_preference', 'allincludinghidden', ''),
array('block_myoverview_user_grouping_preference', 'all', ''),
array('block_myoverview_user_grouping_preference', 'inprogress', ''),
$blockpreferences->{$name}->description
);
}
-}
\ No newline at end of file
+}
{
}
}}
-<div class="header d-flex flex-wrap">
+<div class="header d-flex flex-wrap p-1">
{{> core_calendar/view_selector}}
{{#filter_selector}}
{{{filter_selector}}}
</div>
{{#js}}
require(['jquery'], function($) {
- require(['theme_boost/popover'], function() {
+ require(['theme_boost/bootstrap/popover'], function() {
var target = $("#calendar-day-popover-link-{{courseid}}-{{year}}-{{yday}}-{{uniqid}}");
target.popover({
content: function() {
data-new-event-timestamp="{{neweventtimestamp}}">
<div class="d-none d-md-block hidden-phone text-xs-center">
{{#hasevents}}
- <a data-action="view-day-link" href="#" class="day" aria-label="{{viewdaylinktitle}}"
+ <a data-action="view-day-link" href="#" class="aalink day" aria-label="{{viewdaylinktitle}}"
data-year="{{date.year}}" data-month="{{date.mon}}" data-day="{{mday}}"
data-courseid="{{courseid}}" data-categoryid="{{categoryid}}"
data-timestamp="{{timestamp}}">{{mday}}</a>
</div>
<div class="d-md-none hidden-desktop hidden-tablet">
{{#hasevents}}
- <a data-action="view-day-link" href="#" class="day" aria-label="{{viewdaylinktitle}}"
+ <a data-action="view-day-link" href="#" class="day aalink" aria-label="{{viewdaylinktitle}}"
data-year="{{date.year}}" data-month="{{date.mon}}" data-day="{{mday}}"
data-courseid="{{courseid}}" data-categoryid="{{categoryid}}"
data-timestamp="{{timestamp}}">{{mday}}</a>
$accepted = $cb->get_supported_extensions_as_string($context);
if (!empty($accepted)) {
$importurl = new moodle_url('/contentbank/upload.php', ['contextid' => $contextid]);
- $toolbar[] = array('name' => 'Upload', 'link' => $importurl, 'icon' => 'i/upload');
+ $toolbar[] = array('name' => get_string('upload', 'contentbank'), 'link' => $importurl, 'icon' => 'i/upload');
}
}
}
// Prepare the list of core_course_list_element objects.
foreach ($ids as $id) {
- $courses[$id] = new core_course_list_element($records[$id]);
+ // If a course is deleted after we got the cache entry it may not exist in the database anymore.
+ if (!empty($records[$id])) {
+ $courses[$id] = new core_course_list_element($records[$id]);
+ }
}
}
return $courses;
}
// Prepare the list of core_course_list_element objects.
foreach ($ids as $id) {
- $courses[$id] = new core_course_list_element($records[$id]);
+ // If a course is deleted after we got the cache entry it may not exist in the database anymore.
+ if (!empty($records[$id])) {
+ $courses[$id] = new core_course_list_element($records[$id]);
+ }
}
}
return $courses;
if (!$cb->delete_contents($this->get_context())) {
throw new moodle_exception('errordeletingcontentfromcategory', 'contentbank', '', $this->get_formatted_name());
}
- if (!question_delete_course_category($this, 0, $showfeedback)) {
+ if (!question_delete_course_category($this, null)) {
throw new moodle_exception('cannotdeletecategoryquestions', '', '', $this->get_formatted_name());
}
);
}
}
- if (!question_delete_course_category($this, $newparentcat, $showfeedback)) {
+ if (!question_delete_course_category($this, $newparentcat)) {
if ($showfeedback) {
echo $OUTPUT->notification(get_string('errordeletingquestionsfromcategory', 'question', $catname), 'notifysuccess');
}
$html .= html_writer::end_div();
$html .= $icon;
if ($hasactions) {
- $textattributes = array('class' => 'float-left categoryname');
+ $textattributes = array('class' => 'float-left categoryname aalink');
} else {
$textattributes = array('class' => 'float-left categoryname without-actions');
}
'for' => 'courselistitem' . $course->id));
$html .= html_writer::end_div();
$html .= html_writer::end_div();
- $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename'));
+ $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename aalink'));
$html .= html_writer::start_div('float-right');
if ($course->idnumber) {
$html .= html_writer::tag('span', s($course->idnumber), array('class' => 'text-muted idnumber'));
$html .= html_writer::end_div();
}
$html .= html_writer::end_div();
- $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename'));
+ $html .= html_writer::link($viewcourseurl, $text, array('class' => 'float-left coursename aalink'));
$html .= html_writer::tag('span', $categoryname, array('class' => 'float-left ml-3 text-muted'));
$html .= html_writer::start_div('float-right');
$html .= $this->search_listitem_actions($course);
}
}
- // Delete activity context questions and question categories.
- $showinfo = !defined('AJAX_SCRIPT') || AJAX_SCRIPT == '0';
-
- question_delete_activity($cm, $showinfo);
+ question_delete_activity($cm);
// Call the delete_instance function, if it returns false throw an exception.
if (!$deleteinstancefunction($cm->instance)) {
'class' => 'iconlarge activityicon', 'alt' => '', 'role' => 'presentation', 'aria-hidden' => 'true')) .
html_writer::tag('span', $instancename . $altname, array('class' => 'instancename'));
if ($mod->uservisible) {
- $output .= html_writer::link($url, $activitylink, array('class' => $linkclasses, 'onclick' => $onclick));
+ $output .= html_writer::link($url, $activitylink, array('class' => 'aalink' . $linkclasses, 'onclick' => $onclick));
} else {
// We may be displaying this just in order to show information
// about visibility, without the actual link ($mod->is_visible_on_course_page()).
return $content;
}
+ /**
+ * Returns HTML to display course name.
+ *
+ * @param coursecat_helper $chelper
+ * @param core_course_list_element $course
+ * @return string
+ */
+ protected function course_name(coursecat_helper $chelper, core_course_list_element $course): string {
+ $content = '';
+ if ($chelper->get_show_courses() >= self::COURSECAT_SHOW_COURSES_EXPANDED) {
+ $nametag = 'h3';
+ } else {
+ $nametag = 'div';
+ }
+ $coursename = $chelper->get_course_formatted_name($course);
+ $coursenamelink = html_writer::link(new moodle_url('/course/view.php', ['id' => $course->id]),
+ $coursename, ['class' => $course->visible ? 'aalink' : 'aalink dimmed']);
+ $content .= html_writer::tag($nametag, $coursenamelink, ['class' => 'coursename']);
+ // If we display course in collapsed form but the course has summary or course contacts, display the link to the info page.
+ $content .= html_writer::start_tag('div', ['class' => 'moreinfo']);
+ if ($chelper->get_show_courses() < self::COURSECAT_SHOW_COURSES_EXPANDED) {
+ if ($course->has_summary() || $course->has_course_contacts() || $course->has_course_overviewfiles()
+ || $course->has_custom_fields()) {
+ $url = new moodle_url('/course/info.php', ['id' => $course->id]);
+ $image = $this->output->pix_icon('i/info', $this->strings->summary);
+ $content .= html_writer::link($url, $image, ['title' => $this->strings->summary]);
+ // Make sure JS file to expand course content is included.
+ $this->coursecat_include_js();
+ }
+ }
+ $content .= html_writer::end_tag('div');
+ return $content;
+ }
+
+ /**
+ * Returns HTML to display course enrolment icons.
+ *
+ * @param core_course_list_element $course
+ * @return string
+ */
+ protected function course_enrolment_icons(core_course_list_element $course): string {
+ $content = '';
+ if ($icons = enrol_get_course_info_icons($course)) {
+ $content .= html_writer::start_tag('div', ['class' => 'enrolmenticons']);
+ foreach ($icons as $icon) {
+ $content .= $this->render($icon);
+ }
+ $content .= html_writer::end_tag('div');
+ }
+ return $content;
+ }
+
/**
* Displays one course in the list of courses.
*
}
$content = '';
$classes = trim('coursebox clearfix '. $additionalclasses);
- if ($chelper->get_show_courses() >= self::COURSECAT_SHOW_COURSES_EXPANDED) {
- $nametag = 'h3';
- } else {
+ if ($chelper->get_show_courses() < self::COURSECAT_SHOW_COURSES_EXPANDED) {
$classes .= ' collapsed';
- $nametag = 'div';
}
// .coursebox
));
$content .= html_writer::start_tag('div', array('class' => 'info'));
-
- // course name
- $coursename = $chelper->get_course_formatted_name($course);
- $coursenamelink = html_writer::link(new moodle_url('/course/view.php', array('id' => $course->id)),
- $coursename, array('class' => $course->visible ? '' : 'dimmed'));
- $content .= html_writer::tag($nametag, $coursenamelink, array('class' => 'coursename'));
- // If we display course in collapsed form but the course has summary or course contacts, display the link to the info page.
- $content .= html_writer::start_tag('div', array('class' => 'moreinfo'));
- if ($chelper->get_show_courses() < self::COURSECAT_SHOW_COURSES_EXPANDED) {
- if ($course->has_summary() || $course->has_course_contacts() || $course->has_course_overviewfiles()
- || $course->has_custom_fields()) {
- $url = new moodle_url('/course/info.php', array('id' => $course->id));
- $image = $this->output->pix_icon('i/info', $this->strings->summary);
- $content .= html_writer::link($url, $image, array('title' => $this->strings->summary));
- // Make sure JS file to expand course content is included.
- $this->coursecat_include_js();
- }
- }
- $content .= html_writer::end_tag('div'); // .moreinfo
-
- // print enrolmenticons
- if ($icons = enrol_get_course_info_icons($course)) {
- $content .= html_writer::start_tag('div', array('class' => 'enrolmenticons'));
- foreach ($icons as $pix_icon) {
- $content .= $this->render($pix_icon);
- }
- $content .= html_writer::end_tag('div'); // .enrolmenticons
- }
-
- $content .= html_writer::end_tag('div'); // .info
+ $content .= $this->course_name($chelper, $course);
+ $content .= $this->course_enrolment_icons($course);
+ $content .= html_writer::end_tag('div');
$content .= html_writer::start_tag('div', array('class' => 'content'));
$content .= $this->coursecat_coursebox_content($chelper, $course);
- $content .= html_writer::end_tag('div'); // .content
+ $content .= html_writer::end_tag('div');
$content .= html_writer::end_tag('div'); // .coursebox
return $content;
}
/**
- * Returns HTML to display course content (summary, course contacts and optionally category name)
- *
- * This method is called from coursecat_coursebox() and may be re-used in AJAX
+ * Returns HTML to display course summary.
*
- * @param coursecat_helper $chelper various display options
- * @param stdClass|core_course_list_element $course
+ * @param coursecat_helper $chelper
+ * @param core_course_list_element $course
* @return string
*/
- protected function coursecat_coursebox_content(coursecat_helper $chelper, $course) {
- global $CFG;
- if ($chelper->get_show_courses() < self::COURSECAT_SHOW_COURSES_EXPANDED) {
- return '';
- }
- if ($course instanceof stdClass) {
- $course = new core_course_list_element($course);
- }
+ protected function course_summary(coursecat_helper $chelper, core_course_list_element $course): string {
$content = '';
-
- // display course summary
if ($course->has_summary()) {
- $content .= html_writer::start_tag('div', array('class' => 'summary'));
+ $content .= html_writer::start_tag('div', ['class' => 'summary']);
$content .= $chelper->get_course_formatted_summary($course,
- array('overflowdiv' => true, 'noclean' => true, 'para' => false));
- $content .= html_writer::end_tag('div'); // .summary
+ array('overflowdiv' => true, 'noclean' => true, 'para' => false));
+ $content .= html_writer::end_tag('div');
}
+ return $content;
+ }
+
+ /**
+ * Returns HTML to display course contacts.
+ *
+ * @param core_course_list_element $course
+ * @return string
+ */
+ protected function course_contacts(core_course_list_element $course) {
+ $content = '';
+ if ($course->has_course_contacts()) {
+ $content .= html_writer::start_tag('ul', ['class' => 'teachers']);
+ foreach ($course->get_course_contacts() as $coursecontact) {
+ $rolenames = array_map(function ($role) {
+ return $role->displayname;
+ }, $coursecontact['roles']);
+ $name = implode(", ", $rolenames).': '.
+ html_writer::link(new moodle_url('/user/view.php',
+ ['id' => $coursecontact['user']->id, 'course' => SITEID]),
+ $coursecontact['username']);
+ $content .= html_writer::tag('li', $name);
+ }
+ $content .= html_writer::end_tag('ul');
+ }
+ return $content;
+ }
+
+ /**
+ * Returns HTML to display course overview files.
+ *
+ * @param core_course_list_element $course
+ * @return string
+ */
+ protected function course_overview_files(core_course_list_element $course): string {
+ global $CFG;
- // display course overview files
$contentimages = $contentfiles = '';
foreach ($course->get_course_overviewfiles() as $file) {
$isimage = $file->is_valid_image();
- $url = file_encode_url("$CFG->wwwroot/pluginfile.php",
- '/'. $file->get_contextid(). '/'. $file->get_component(). '/'.
- $file->get_filearea(). $file->get_filepath(). $file->get_filename(), !$isimage);
+ $url = moodle_url::make_file_url("$CFG->wwwroot/pluginfile.php",
+ '/' . $file->get_contextid() . '/' . $file->get_component() . '/' .
+ $file->get_filearea() . $file->get_filepath() . $file->get_filename(), !$isimage);
if ($isimage) {
$contentimages .= html_writer::tag('div',
- html_writer::empty_tag('img', array('src' => $url)),
- array('class' => 'courseimage'));
+ html_writer::empty_tag('img', ['src' => $url]),
+ ['class' => 'courseimage']);
} else {
$image = $this->output->pix_icon(file_file_icon($file, 24), $file->get_filename(), 'moodle');
- $filename = html_writer::tag('span', $image, array('class' => 'fp-icon')).
- html_writer::tag('span', $file->get_filename(), array('class' => 'fp-filename'));
+ $filename = html_writer::tag('span', $image, ['class' => 'fp-icon']).
+ html_writer::tag('span', $file->get_filename(), ['class' => 'fp-filename']);
$contentfiles .= html_writer::tag('span',
- html_writer::link($url, $filename),
- array('class' => 'coursefile fp-filename-icon'));
+ html_writer::link($url, $filename),
+ ['class' => 'coursefile fp-filename-icon']);
}
}
- $content .= $contentimages. $contentfiles;
-
- // Display course contacts. See core_course_list_element::get_course_contacts().
- if ($course->has_course_contacts()) {
- $content .= html_writer::start_tag('ul', array('class' => 'teachers'));
- foreach ($course->get_course_contacts() as $coursecontact) {
- $rolenames = array_map(function ($role) {
- return $role->displayname;
- }, $coursecontact['roles']);
- $name = implode(", ", $rolenames).': '.
- html_writer::link(new moodle_url('/user/view.php',
- array('id' => $coursecontact['user']->id, 'course' => SITEID)),
- $coursecontact['username']);
- $content .= html_writer::tag('li', $name);
- }
- $content .= html_writer::end_tag('ul'); // .teachers
- }
+ return $contentimages . $contentfiles;
+ }
- // display course category if necessary (for example in search results)
+ /**
+ * Returns HTML to display course category name.
+ *
+ * @param coursecat_helper $chelper
+ * @param core_course_list_element $course
+ * @return string
+ */
+ protected function course_category_name(coursecat_helper $chelper, core_course_list_element $course): string {
+ $content = '';
+ // Display course category if necessary (for example in search results).
if ($chelper->get_show_courses() == self::COURSECAT_SHOW_COURSES_EXPANDED_WITH_CAT) {
if ($cat = core_course_category::get($course->category, IGNORE_MISSING)) {
- $content .= html_writer::start_tag('div', array('class' => 'coursecat'));
+ $content .= html_writer::start_tag('div', ['class' => 'coursecat']);
$content .= get_string('category').': '.
- html_writer::link(new moodle_url('/course/index.php', array('categoryid' => $cat->id)),
- $cat->get_formatted_name(), array('class' => $cat->visible ? '' : 'dimmed'));
- $content .= html_writer::end_tag('div'); // .coursecat
+ html_writer::link(new moodle_url('/course/index.php', ['categoryid' => $cat->id]),
+ $cat->get_formatted_name(), ['class' => $cat->visible ? '' : 'dimmed']);
+ $content .= html_writer::end_tag('div');
}
}
+ return $content;
+ }
- // Display custom fields.
+ /**
+ * Returns HTML to display course custom fields.
+ *
+ * @param core_course_list_element $course
+ * @return string
+ */
+ protected function course_custom_fields(core_course_list_element $course): string {
+ $content = '';
if ($course->has_custom_fields()) {
$handler = core_course\customfield\course_handler::create();
$customfields = $handler->display_custom_fields_data($course->get_custom_fields());
$content .= \html_writer::tag('div', $customfields, ['class' => 'customfields-container']);
}
+ return $content;
+ }
+ /**
+ * Returns HTML to display course content (summary, course contacts and optionally category name)
+ *
+ * This method is called from coursecat_coursebox() and may be re-used in AJAX
+ *
+ * @param coursecat_helper $chelper various display options
+ * @param stdClass|core_course_list_element $course
+ * @return string
+ */
+ protected function coursecat_coursebox_content(coursecat_helper $chelper, $course) {
+ if ($chelper->get_show_courses() < self::COURSECAT_SHOW_COURSES_EXPANDED) {
+ return '';
+ }
+ if ($course instanceof stdClass) {
+ $course = new core_course_list_element($course);
+ }
+ $content = $this->course_summary($chelper, $course);
+ $content .= $this->course_overview_files($course);
+ $content .= $this->course_contacts($course);
+ $content .= $this->course_category_name($chelper, $course);
+ $content .= $this->course_custom_fields($course);
return $content;
}
}
$content .= html_writer::start_tag('div', array('class' => 'info'));
- $content .= html_writer::tag(($depth > 1) ? 'h4' : 'h3', $categoryname, array('class' => 'categoryname'));
+ $content .= html_writer::tag(($depth > 1) ? 'h4' : 'h3', $categoryname, array('class' => 'categoryname aabtn'));
$content .= html_writer::end_tag('div'); // .info
// add category content to the output
if ($coursecat->get_children_count()) {
$classes = array(
- 'collapseexpand',
+ 'collapseexpand', 'aabtn'
);
// Check if the category content contains subcategories with children's content loaded.
}
$output = html_writer::link('#' . $skipdivid,
get_string('skipa', 'access', core_text::strtolower(strip_tags($header))),
- array('class' => 'skip-block skip'));
+ array('class' => 'skip-block skip aabtn'));
// Wrap frontpage part in div container.
$output .= html_writer::start_tag('div', array('id' => $contentsdivid));
</div>
{{/showshortname}}
</div>
- <a href="{{viewurl}}" class="coursename mr-2">
+ <a href="{{viewurl}}" class="aalink coursename mr-2">
{{> core_course/favouriteicon }}
<span class="sr-only">
{{#str}}aria:coursename, core_course{{/str}}
"icon": "<img class='icon' src='http://urltooptionicon'>"
}
}}
-<div role="menuitem" tabindex="-1" aria-label="{{title}}" class="option d-block text-center py-1 mb-1" data-region="chooser-option-container" data-internal="{{name}}" data-modname="{{componentname}}_{{link}}">
+<div role="menuitem" tabindex="-1" aria-label="{{title}}" class="option d-block text-center py-1 mb-1 aabtn" data-region="chooser-option-container" data-internal="{{name}}" data-modname="{{componentname}}_{{link}}">
<div class="optioninfo w-100" data-region="chooser-option-info-container">
<a class="d-block" href="{{link}}" title="{{#str}} addnew, moodle, {{title}} {{/str}}" tabindex="-1" data-action="add-chooser-option">
<span class="optionicon d-block">
$exception = new ExpectationException('"' . $categoryname . '" category can not be found', $this->getSession());
$categoryliteral = behat_context_helper::escape($categoryname);
- $xpath = "//div[@class='info']/descendant::*[" . implode(' or ', $headingtags) . "][@class='categoryname'][./descendant::a[.=$categoryliteral]]";
+ $xpath = "//div[@class='info']/descendant::*[" . implode(' or ', $headingtags) .
+ "][contains(@class,'categoryname')][./descendant::a[.=$categoryliteral]]";
$node = $this->find('xpath', $xpath, $exception);
$node->click();
}
return $draftid;
}
+
+ /**
+ * This test ensures that is the list of courses in a category can be retrieved while a course is being deleted.
+ */
+ public function test_get_courses_during_delete() {
+ global $DB;
+ $category = self::getDataGenerator()->create_category();
+ $course = self::getDataGenerator()->create_course(['category' => $category->id]);
+ $othercourse = self::getDataGenerator()->create_course(['category' => $category->id]);
+ $coursecategory = core_course_category::get($category->id);
+ // Get a list of courses before deletion to populate the cache.
+ $originalcourses = $coursecategory->get_courses();
+ $this->assertCount(2, $originalcourses);
+ $this->assertArrayHasKey($course->id, $originalcourses);
+ $this->assertArrayHasKey($othercourse->id, $originalcourses);
+ // Simulate the course deletion process being part way though.
+ $DB->delete_records('course', ['id' => $course->id]);
+ // Get the list of courses while a deletion is in progress.
+ $courses = $coursecategory->get_courses();
+ $this->assertCount(1, $courses);
+ $this->assertArrayHasKey($othercourse->id, $courses);
+ }
}
case 'quiz':
$qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
$qcat = $qgen->create_question_category(array('contextid' => $modcontext->id));
- $questions = array(
- $qgen->create_question('shortanswer', null, array('category' => $qcat->id)),
- $qgen->create_question('shortanswer', null, array('category' => $qcat->id)),
- );
- $this->expectOutputRegex('/'.get_string('unusedcategorydeleted', 'question').'/');
+ $qgen->create_question('shortanswer', null, array('category' => $qcat->id));
+ $qgen->create_question('shortanswer', null, array('category' => $qcat->id));
break;
default:
break;
* The function get_module_metadata is now deprecated. Please use \core_course\local\service\content_item_service instead.
* Activity module names are now PARAM_ALPHANUM instead of PARAM_ALPHA so integers can be used in activity module names
+* The following functions have been added to core_course_renderer class to have more granularity. They can be overriden in
+ extending classes:
+ - course_name
+ - course_enrolment_icons
+ - course_summary
+ - course_contacts
+ - course_overview_files
+ - course_category_name
+ - course_custom_fields
=== 3.8 ===
echo \html_writer::end_tag('tr');
}
+ /**
+ * Method to define whether the dataformat supports export of HTML
+ *
+ * @return bool
+ */
+ public function supports_html(): bool {
+ return true;
+ }
+
/**
* Write a single record
*
* @param int $rownum
*/
public function write_record($record, $rownum) {
+ $record = $this->format_record($record);
+
echo \html_writer::start_tag('tr');
foreach ($record as $cell) {
echo \html_writer::tag('td', $cell);
echo ",";
}
- echo json_encode($record, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+ echo json_encode($this->format_record($record), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
$this->sheetdatadded = true;
}
$this->print_heading();
}
+ /**
+ * Method to define whether the dataformat supports export of HTML
+ *
+ * @return bool
+ */
+ public function supports_html(): bool {
+ return true;
+ }
+
+ /**
+ * When exporting images, we need to return their Base64 encoded content. Otherwise TCPDF will create a HTTP
+ * request for them, which will lead to the login page (i.e. not the image it expects) and throw an exception
+ *
+ * Note: ideally we would copy the file to a temp location and return it's path, but a bug in TCPDF currently
+ * prevents that
+ *
+ * @param \stored_file $file
+ * @return string|null
+ */
+ protected function export_html_image_source(\stored_file $file): ?string {
+ // Set upper dimensions for embedded images.
+ $resizedimage = $file->resize_image(400, 300);
+
+ return '@' . base64_encode($resizedimage);
+ }
+
+ /**
+ * Write a single record
+ *
+ * @param array $record
+ * @param int $rownum
+ */
public function write_record($record, $rownum) {
$rowheight = 0;
- // If $record is an object convert it to an array.
- if (is_object($record)) {
- $record = (array)$record;
- }
-
+ $record = $this->format_record($record);
foreach ($record as $cell) {
- $rowheight = max($rowheight, $this->pdf->getStringHeight($this->colwidth, $cell, false, true, '', 1));
+ // We need to calculate the row height (accounting for any content). Unfortunately TCPDF doesn't provide an easy
+ // method to do that, so we create a second PDF inside a transaction, add cell content and use the largest cell by
+ // height. Solution similar to that at https://stackoverflow.com/a/1943096.
+ $pdf2 = clone $this->pdf;
+ $pdf2->startTransaction();
+ $pdf2->AddPage('L');
+ $pdf2->writeHTMLCell($this->colwidth, 0, '', '', $cell, 1, 1, false, true, 'L');
+ $rowheight = max($rowheight, $pdf2->getY() - $pdf2->getMargins()['top']);
+ $pdf2->rollbackTransaction();
}
$margins = $this->pdf->getMargins();
// Determine whether we're at the last element of the record.
$nextposition = ($lastkey === $key) ? 1 : 0;
// Write the element.
- $this->pdf->Multicell($this->colwidth, $rowheight, $cell, 1, 'L', false, $nextposition);
+ $this->pdf->writeHTMLCell($this->colwidth, $rowheight, '', '', $cell, 1, $nextposition, false, true, 'L');
}
}
--- /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 the dataformat_pdf writer
+ *
+ * @package dataformat_pdf
+ * @copyright 2020 Paul Holden <paulh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace dataformat_pdf;
+
+use core\dataformat;
+use context_system;
+use html_writer;
+use moodle_url;
+
+/**
+ * Writer tests
+ *
+ * @package dataformat_pdf
+ * @copyright 2020 Paul Holden <paulh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class writer_testcase extends \advanced_testcase {
+
+ /**
+ * Test writing data whose content contains an image with pluginfile.php source
+ */
+ public function test_write_data_with_pluginfile_image(): void {
+ global $CFG;
+
+ $this->resetAfterTest(true);
+
+ $imagefixture = "{$CFG->dirroot}/lib/filestorage/tests/fixtures/testimage.jpg";
+ $image = get_file_storage()->create_file_from_pathname([
+ 'contextid' => context_system::instance()->id,
+ 'component' => 'dataformat_pdf',
+ 'filearea' => 'test',
+ 'itemid' => 0,
+ 'filepath' => '/',
+ 'filename' => basename($imagefixture),
+
+ ], $imagefixture);
+
+ $imageurl = moodle_url::make_pluginfile_url($image->get_contextid(), $image->get_component(), $image->get_filearea(),
+ $image->get_itemid(), $image->get_filepath(), $image->get_filename());
+
+ // Insert out test image into the data so it is exported.
+ $columns = ['animal', 'image'];
+ $row = ['cat', html_writer::img($imageurl->out(), 'My image')];
+
+ // Export to file. Assert that the exported file exists.
+ $exportfile = dataformat::write_data('My export', 'pdf', $columns, [$row]);
+ $this->assertFileExists($exportfile);
+
+ // The exported file should be a reasonable size (~275kb).
+ $this->assertGreaterThan(270000, filesize($exportfile));
+ }
+}
* Calls to the following dataformat plugin methods have been removed:
- write_header()
- write_footer()
+* The following methods have been added to the base class to allow instances to define support for exporting
+ HTML content, with additional support for defining how images should be embedded:
+ - supports_html()
+ - export_html_image_source()
+* Dataformat writers should also call the following method to ensure data is properly formatted before being
+ written, which takes into account prior methods defining support for HTML:
+ - format_record()
=== 3.4 ===
* In order to allow multiple sheets in an exported file the functions write_header() and write_footer() have
And I should see "Student 001"
And I click on "Enrol users" "button" in the "Enrol users" "dialogue"
Then I should see "Active" in the "Student 001" "table_row"
- # The following line is commented out as auto-hidden toasts fire events in the wrong place.
- # TODO Uncomment this when we upgrade Bootstrap. This issue is fixed in v4.4.0 - see MDL-67386.
- #And I should see "1 enrolled users"
+ And I should see "1 enrolled users"
@javascript
Scenario: Searching for a non-existing user
protected function fm_js_template_iconfilename() {
$rv = '
<div class="fp-file">
- <a href="#">
+ <a href="#" class="d-block aabtn">
<div style="position:relative;">
<div class="fp-thumbnail"></div>
<div class="fp-reficons1"></div>
<div class="fp-filename text-truncate"></div>
</div>
</a>
- <a class="fp-contextmenu" href="#">'.$this->pix_icon('i/menu', 'â–¶').'</a>
+ <a class="fp-contextmenu btn btn-icon btn-light border icon-no-margin icon-size-3" href="#">
+ <span>'.$this->pix_icon('i/menu', 'â–¶').'</span></a>
</div>';
return $rv;
}
<div class="filemanager fp-mkdir-dlg" role="dialog" aria-live="assertive" aria-labelledby="fp-mkdir-dlg-title">
<div class="fp-mkdir-dlg-text">
<label id="fp-mkdir-dlg-title">' . get_string('newfoldername', 'repository') . '</label><br/>
- <input type="text" />
+ <input type="text" class="form-control"/>
</div>
<button class="fp-dlg-butcreate btn-primary btn">'.get_string('makeafolder').'</button>
<button class="fp-dlg-butcancel btn-cancel btn">'.get_string('cancel').'</button>
redirect($returnurl);
} else if ($data = $mform->get_data()) {
+
+ // Make sure we are updating the cache.
+ $cache = cache::make('core', 'grade_letters');
+
if (!$admin and empty($data->override)) {
$records = $DB->get_records('grade_letters', array('contextid' => $context->id));
foreach ($records as $record) {
));
$event->trigger();
}
+
+ // Make sure we clear the cache for this context.
+ $cache->delete($context->id);
redirect($returnurl);
}
}
}
+ // Cache the changed letters.
+ if (!empty($letters)) {
+
+ // For some reason, the cache saves it in the order in which they were entered
+ // but we really want to order them in descending order so we sort it here.
+ krsort($letters);
+ $cache->set($context->id, $letters);
+ }
+
// Delete the unused records.
foreach($pool as $leftover) {
$DB->delete_records('grade_letters', array('id' => $leftover->id));
$rows = $this->get_left_icons_row($rows, $colspan);
$suspendedstring = null;
+
+ $usercount = 0;
foreach ($this->users as $userid => $user) {
$userrow = new html_table_row();
$userrow->id = 'fixed_user_'.$userid;
+ $userrow->attributes['class'] = ($usercount % 2) ? 'userrow even' : 'userrow odd';
$usercell = new html_table_cell();
- $usercell->attributes['class'] = 'header user';
+ $usercell->attributes['class'] = ($usercount % 2) ? 'header user even' : 'header user odd';
+ $usercount++;
$usercell->header = true;
$usercell->scope = 'row';
white-space: nowrap;
}
-/**
- * Stripped table.
- */
-.path-grade-report-grader .gradeparent tr:nth-of-type(even) .cell {
- background-color: #f9f9f9;
-}
-
/**
* All the floating divs.
*/
text-align: left;
}
-/**
- * All the floating cells.
- */
-.path-grade-report-grader .gradeparent .floater .cell {
- background-color: #f9f9f9;
-}
-
/**
* The user cells.
*/
float: left;
}
-.path-grade-report .gradeparent .floater .controls.cell,
-.path-grade-report-grader .gradeparent .controls {
- background-color: #f3ead8;
-}
-
.path-grade-report-grader .gradeparent .category {
text-align: left;
}
$this->currentgroup = -2; // means can not access any groups at all
}
if ($this->currentgroup) {
- $group = groups_get_group($this->currentgroup);
- $this->currentgroupname = $group->name;
+ if ($group = groups_get_group($this->currentgroup)) {
+ $this->currentgroupname = $group->name;
+ }
$this->groupsql = " JOIN {groups_members} gm ON gm.userid = u.id ";
$this->groupwheresql = " AND gm.groupid = :gr_grpid ";
$this->groupwheresql_params = array('gr_grpid'=>$this->currentgroup);
return ($h5p) ? $h5p : null;
}
+
+ /**
+ * Return the H5P export information file when the file has been deployed.
+ * Otherwise, return null if H5P file:
+ * i) has not been deployed.
+ * ii) has changed the content.
+ *
+ * The information returned will be:
+ * - filename, filepath, mimetype, filesize, timemodified and fileurl.
+ *
+ * @param int $contextid ContextId of the H5P activity.
+ * @param factory $factory The \core_h5p\factory object.
+ * @param string $component component
+ * @param string $filearea file area
+ * @return array|null Return file info otherwise null.
+ */
+ public static function get_export_info_from_context_id(int $contextid,
+ factory $factory,
+ string $component,
+ string $filearea): ?array {
+
+ $core = $factory->get_core();
+ $fs = get_file_storage();
+ $files = $fs->get_area_files($contextid, $component, $filearea, 0, 'id', false);
+ $file = reset($files);
+
+ if ($h5p = self::get_content_from_pathnamehash($file->get_pathnamehash())) {
+ if ($h5p->contenthash == $file->get_contenthash()) {
+ $content = $core->loadContent($h5p->id);
+ $slug = $content['slug'] ? $content['slug'] . '-' : '';
+ $filename = "{$slug}{$content['id']}.h5p";
+ $deployedfile = helper::get_export_info($filename, null, $factory);
+
+ return $deployedfile;
+ }
+ }
+
+ return null;
+ }
}
return $strings;
}
+
+ /**
+ * Get the information related to the H5P export file.
+ * The information returned will be:
+ * - filename, filepath, mimetype, filesize, timemodified and fileurl.
+ *
+ * @param string $exportfilename The H5P export filename (with slug).
+ * @param \moodle_url $url The URL of the exported file.
+ * @param factory $factory The \core_h5p\factory object
+ * @return array|null The information export file otherwise null.
+ */
+ public static function get_export_info(string $exportfilename, \moodle_url $url = null, ?factory $factory = null): ?array {
+
+ if (!$factory) {
+ $factory = new factory();
+ }
+ $core = $factory->get_core();
+
+ // Get export file.
+ if (!$fileh5p = $core->fs->get_export_file($exportfilename)) {
+ return null;
+ }
+
+ // Build the export info array.
+ $file = [];
+ $file['filename'] = $fileh5p->get_filename();
+ $file['filepath'] = $fileh5p->get_filepath();
+ $file['mimetype'] = $fileh5p->get_mimetype();
+ $file['filesize'] = $fileh5p->get_filesize();
+ $file['timemodified'] = $fileh5p->get_timemodified();
+
+ if (!$url) {
+ $url = \moodle_url::make_webservice_pluginfile_url(
+ $fileh5p->get_contextid(),
+ $fileh5p->get_component(),
+ $fileh5p->get_filearea(),
+ '',
+ '',
+ $fileh5p->get_filename()
+ );
+ }
+
+ $file['fileurl'] = $url->out(false);
+
+ return $file;
+ }
}
}
/**
- * Return the export file for Mobile App.
+ * Return the info export file for Mobile App.
*
* @return array
*/
$path = $exporturl->out_as_local_url();
$parts = explode('/', $path);
$filename = array_pop($parts);
- // Get the the export file.
- $systemcontext = \context_system::instance();
- $fs = get_file_storage();
- $fileh5p = $fs->get_file($systemcontext->id,
- \core_h5p\file_storage::COMPONENT,
- \core_h5p\file_storage::EXPORT_FILEAREA,
- 0,
- '/',
- $filename);
- // Get the options that the Mobile App needs.
- $file = [];
- $file['filename'] = $fileh5p->get_filename();
- $file['filepath'] = $fileh5p->get_filepath();
- $file['mimetype'] = $fileh5p->get_mimetype();
- $file['filesize'] = $fileh5p->get_filesize();
- $file['timemodified'] = $fileh5p->get_timemodified();
- $file['fileurl'] = $exporturl->out(false);
+ // Get the required info from the export file to be able to get the export file by third apps.
+ $file = helper::get_export_info($filename, $exporturl);
return $file;
}
api::delete_content_from_pluginfile_url($url->out(), $factory);
$this->assertEquals(0, $DB->count_records('h5p'));
}
+
+ /**
+ * Test the behaviour of get_export_info_from_context_id().
+ */
+ public function test_get_export_info_from_context_id(): void {
+ global $DB;
+
+ $this->setRunTestInSeparateProcess(true);
+ $this->resetAfterTest();
+ $factory = new factory();
+
+ // Create the H5P data.
+ $filename = 'find-the-words.h5p';
+ $syscontext = \context_system::instance();
+
+ // Test scenario 1: H5P exists and deployed.
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
+ $fakeexportfile = $generator->create_export_file($filename,
+ $syscontext->id,
+ \core_h5p\file_storage::COMPONENT,
+ \core_h5p\file_storage::EXPORT_FILEAREA);
+
+ $exportfile = api::get_export_info_from_context_id($syscontext->id,
+ $factory,
+ \core_h5p\file_storage::COMPONENT,
+ \core_h5p\file_storage::EXPORT_FILEAREA);
+ $this->assertEquals($fakeexportfile['filename'], $exportfile['filename']);
+ $this->assertEquals($fakeexportfile['filepath'], $exportfile['filepath']);
+ $this->assertEquals($fakeexportfile['filesize'], $exportfile['filesize']);
+ $this->assertEquals($fakeexportfile['timemodified'], $exportfile['timemodified']);
+ $this->assertEquals($fakeexportfile['fileurl'], $exportfile['fileurl']);
+
+ // Test scenario 2: H5P exist, deployed but the content has changed.
+ // We need to change the contenthash to simulate the H5P file was changed.
+ $h5pfile = $DB->get_record('h5p', []);
+ $h5pfile->contenthash = sha1('testedit');
+ $DB->update_record('h5p', $h5pfile);
+ $exportfile = api::get_export_info_from_context_id($syscontext->id,
+ $factory,
+ \core_h5p\file_storage::COMPONENT,
+ \core_h5p\file_storage::EXPORT_FILEAREA);
+ $this->assertNull($exportfile);
+
+ // Tests scenario 3: H5P is not deployed.
+ // We need to delete the H5P record to simulate the H5P was not deployed.
+ $DB->delete_records('h5p', ['id' => $h5pfile->id]);
+ $exportfile = api::get_export_info_from_context_id($syscontext->id,
+ $factory,
+ \core_h5p\file_storage::COMPONENT,
+ \core_h5p\file_storage::EXPORT_FILEAREA);
+ $this->assertNull($exportfile);
+ }
}
* test_get_trusted_h5p_file description
*/
public function test_get_trusted_h5p_file() {
- global $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
// This is a valid .H5P file.
$filename = 'find-the-words.h5p';
- $path = __DIR__ . '/fixtures/'.$filename;
$syscontext = \context_system::instance();
- $filerecord = [
- 'contextid' => $syscontext->id,
- 'component' => \core_h5p\file_storage::COMPONENT,
- 'filearea' => 'unittest',
- 'itemid' => 0,
- 'filepath' => '/',
- 'filename' => $filename,
- ];
- // Load the h5p file into DB.
- $fs = get_file_storage();
- $file = $fs->create_file_from_pathname($filerecord, $path);
+
+ // Create a fake export H5P file with normal pluginfile call.
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
+ $deployedfile = $generator->create_export_file($filename,
+ $syscontext->id,
+ \core_h5p\file_storage::COMPONENT,
+ \core_h5p\file_storage::EXPORT_FILEAREA,
+ $generator::PLUGINFILE);
+
// Make the URL to pass to the WS.
$url = \moodle_url::make_pluginfile_url(
$syscontext->id,
\core_h5p\file_storage::COMPONENT,
- 'unittest',
+ \core_h5p\file_storage::EXPORT_FILEAREA,
0,
'/',
$filename
);
+
// Call the WS.
- $result = external::get_trusted_h5p_file($url->out(), 0, 0, 0, 0);
+ $result = external::get_trusted_h5p_file($url->out(false), 0, 0, 0, 0);
$result = external_api::clean_returnvalue(external::get_trusted_h5p_file_returns(), $result);
// Expected result: Just 1 record on files and none on warnings.
$this->assertCount(1, $result['files']);
$this->assertCount(0, $result['warnings']);
- // Get the export file in the DB to compare with the ws's results.
- $fileh5p = $this->get_export_file($filename, $file->get_pathnamehash());
- $fileh5purl = \moodle_url::make_pluginfile_url(
- $syscontext->id,
- \core_h5p\file_storage::COMPONENT,
- \core_h5p\file_storage::EXPORT_FILEAREA,
- '',
- '',
- $fileh5p->get_filename()
- );
- $this->assertEquals($fileh5p->get_filepath(), $result['files'][0]['filepath']);
- $this->assertEquals($fileh5p->get_mimetype(), $result['files'][0]['mimetype']);
- $this->assertEquals($fileh5p->get_filesize(), $result['files'][0]['filesize']);
- $this->assertEquals($fileh5p->get_timemodified(), $result['files'][0]['timemodified']);
- $this->assertEquals($fileh5p->get_filename(), $result['files'][0]['filename']);
- $this->assertEquals($fileh5purl->out(), $result['files'][0]['fileurl']);
+
+ // Check info export file to compare with the ws's results.
+ $this->assertEquals($deployedfile['filepath'], $result['files'][0]['filepath']);
+ $this->assertEquals($deployedfile['mimetype'], $result['files'][0]['mimetype']);
+ $this->assertEquals($deployedfile['filesize'], $result['files'][0]['filesize']);
+ $this->assertEquals($deployedfile['timemodified'], $result['files'][0]['timemodified']);
+ $this->assertEquals($deployedfile['filename'], $result['files'][0]['filename']);
+ $this->assertEquals($deployedfile['fileurl'], $result['files'][0]['fileurl']);
}
/**
* using webservice/pluginfile.php as url param.
*/
public function test_allow_webservice_pluginfile_in_url_param() {
- global $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
// This is a valid .H5P file.
$filename = 'find-the-words.h5p';
- $path = __DIR__ . '/fixtures/'.$filename;
$syscontext = \context_system::instance();
- $filerecord = [
- 'contextid' => $syscontext->id,
- 'component' => \core_h5p\file_storage::COMPONENT,
- 'filearea' => 'unittest',
- 'itemid' => 0,
- 'filepath' => '/',
- 'filename' => $filename,
- ];
- // Load the h5p file into DB.
- $fs = get_file_storage();
- $file = $fs->create_file_from_pathname($filerecord, $path);
+
+ // Create a fake export H5P file with webservice call.
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
+ $deployedfile = $generator->create_export_file($filename,
+ $syscontext->id,
+ \core_h5p\file_storage::COMPONENT,
+ \core_h5p\file_storage::EXPORT_FILEAREA);
+
// Make the URL to pass to the WS.
$url = \moodle_url::make_webservice_pluginfile_url(
$syscontext->id,
\core_h5p\file_storage::COMPONENT,
- 'unittest',
+ \core_h5p\file_storage::EXPORT_FILEAREA,
0,
'/',
$filename
);
+
// Call the WS.
$result = external::get_trusted_h5p_file($url->out(), 0, 0, 0, 0);
$result = external_api::clean_returnvalue(external::get_trusted_h5p_file_returns(), $result);
- // Expected result: Just 1 record on files and none on warnings.
- $this->assertCount(1, $result['files']);
- $this->assertCount(0, $result['warnings']);
- // Get the export file in the DB to compare with the ws's results.
- $fileh5p = $this->get_export_file($filename, $file->get_pathnamehash());
- $fileh5purl = \moodle_url::make_webservice_pluginfile_url(
- $syscontext->id,
- \core_h5p\file_storage::COMPONENT,
- \core_h5p\file_storage::EXPORT_FILEAREA,
- '',
- '',
- $fileh5p->get_filename()
- );
- $this->assertEquals($fileh5p->get_filepath(), $result['files'][0]['filepath']);
- $this->assertEquals($fileh5p->get_mimetype(), $result['files'][0]['mimetype']);
- $this->assertEquals($fileh5p->get_filesize(), $result['files'][0]['filesize']);
- $this->assertEquals($fileh5p->get_timemodified(), $result['files'][0]['timemodified']);
- $this->assertEquals($fileh5p->get_filename(), $result['files'][0]['filename']);
- $this->assertEquals($fileh5purl->out(), $result['files'][0]['fileurl']);
+
+ // Check info export file to compare with the ws's results.
+ $this->assertEquals($deployedfile['filepath'], $result['files'][0]['filepath']);
+ $this->assertEquals($deployedfile['mimetype'], $result['files'][0]['mimetype']);
+ $this->assertEquals($deployedfile['filesize'], $result['files'][0]['filesize']);
+ $this->assertEquals($deployedfile['timemodified'], $result['files'][0]['timemodified']);
+ $this->assertEquals($deployedfile['filename'], $result['files'][0]['filename']);
+ $this->assertEquals($deployedfile['fileurl'], $result['files'][0]['fileurl']);
}
/**
* using tokenpluginfile.php as url param.
*/
public function test_allow_tokenluginfile_in_url_param() {
- global $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
// This is a valid .H5P file.
$filename = 'find-the-words.h5p';
- $path = __DIR__ . '/fixtures/'.$filename;
$syscontext = \context_system::instance();
- $filerecord = [
- 'contextid' => $syscontext->id,
- 'component' => \core_h5p\file_storage::COMPONENT,
- 'filearea' => 'unittest',
- 'itemid' => 0,
- 'filepath' => '/',
- 'filename' => $filename,
- ];
- // Load the h5p file into DB.
- $fs = get_file_storage();
- $file = $fs->create_file_from_pathname($filerecord, $path);
+
+ // Create a fake export H5P file with tokenfile call.
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
+ $deployedfile = $generator->create_export_file($filename,
+ $syscontext->id,
+ \core_h5p\file_storage::COMPONENT,
+ \core_h5p\file_storage::EXPORT_FILEAREA,
+ $generator::TOKENPLUGINFILE);
+
// Make the URL to pass to the WS.
$url = \moodle_url::make_pluginfile_url(
$syscontext->id,
\core_h5p\file_storage::COMPONENT,
- 'unittest',
+ \core_h5p\file_storage::EXPORT_FILEAREA,
0,
'/',
$filename,
false,
true
);
+
// Call the WS.
- $result = external::get_trusted_h5p_file($url->out(), 0, 0, 0, 0);
+ $result = external::get_trusted_h5p_file($url->out(false), 0, 0, 0, 0);
$result = external_api::clean_returnvalue(external::get_trusted_h5p_file_returns(), $result);
// Expected result: Just 1 record on files and none on warnings.
$this->assertCount(1, $result['files']);
$this->assertCount(0, $result['warnings']);
- // Get the export file in the DB to compare with the ws's results.
- $fileh5p = $this->get_export_file($filename, $file->get_pathnamehash());
- $fileh5purl = \moodle_url::make_pluginfile_url(
- $syscontext->id,
- \core_h5p\file_storage::COMPONENT,
- \core_h5p\file_storage::EXPORT_FILEAREA,
- '',
- '',
- $fileh5p->get_filename(),
- false,
- true
- );
- $this->assertEquals($fileh5p->get_filepath(), $result['files'][0]['filepath']);
- $this->assertEquals($fileh5p->get_mimetype(), $result['files'][0]['mimetype']);
- $this->assertEquals($fileh5p->get_filesize(), $result['files'][0]['filesize']);
- $this->assertEquals($fileh5p->get_timemodified(), $result['files'][0]['timemodified']);
- $this->assertEquals($fileh5p->get_filename(), $result['files'][0]['filename']);
- $this->assertEquals($fileh5purl->out(), $result['files'][0]['fileurl']);
- }
- /**
- * Get the H5P export file.
- *
- * @param string $filename
- * @param string $pathnamehash
- * @return stored_file
- */
- protected function get_export_file($filename, $pathnamehash) {
- global $DB;
-
- // Simulate the filenameexport using slug as H5P does.
- $id = $DB->get_field('h5p', 'id', ['pathnamehash' => $pathnamehash]);
- $filenameexport = basename($filename, '.h5p').'-'.$id.'-'.$id.'.h5p';
- $syscontext = \context_system::instance();
- $fs = get_file_storage();
- $fileh5p = $fs->get_file($syscontext->id,
- \core_h5p\file_storage::COMPONENT,
- \core_h5p\file_storage::EXPORT_FILEAREA,
- 0,
- '/',
- $filenameexport);
- return $fileh5p;
+ // Check info export file to compare with the ws's results.
+ $this->assertEquals($deployedfile['filepath'], $result['files'][0]['filepath']);
+ $this->assertEquals($deployedfile['mimetype'], $result['files'][0]['mimetype']);
+ $this->assertEquals($deployedfile['filesize'], $result['files'][0]['filesize']);
+ $this->assertEquals($deployedfile['timemodified'], $result['files'][0]['timemodified']);
+ $this->assertEquals($deployedfile['filename'], $result['files'][0]['filename']);
+ $this->assertEquals($deployedfile['fileurl'], $result['files'][0]['fileurl']);
}
}
use core_h5p\local\library\autoloader;
use core_h5p\core;
+use core_h5p\player;
+use core_h5p\factory;
defined('MOODLE_INTERNAL') || die();
*/
class core_h5p_generator extends \component_generator_base {
+ /** Url pointing to webservice plugin file. */
+ public const WSPLUGINFILE = 0;
+ /** Url pointing to token plugin file. */
+ public const TOKENPLUGINFILE = 1;
+ /** Url pointing to plugin file. */
+ public const PLUGINFILE = 2;
+
/**
* Convenience function to create a file.
*
$fs = new file_storage();
return $fs->create_file_from_string($filerecord, $content);
}
+
+ /**
+ * Create a fake export H5P deployed file.
+ *
+ * @param string $filename Name of the H5P file to deploy.
+ * @param int $contextid Context id of the H5P activity.
+ * @param string $component component.
+ * @param string $filearea file area.
+ * @param int $typeurl Type of url to create the export url plugin file.
+ * @return array return deployed file information.
+ */
+ public function create_export_file(string $filename, int $contextid,
+ string $component,
+ string $filearea,
+ int $typeurl = self::WSPLUGINFILE): array {
+ global $CFG;
+
+ // We need the autoloader for H5P player.
+ autoloader::register();
+
+ $path = $CFG->dirroot.'/h5p/tests/fixtures/'.$filename;
+ $filerecord = [
+ 'contextid' => $contextid,
+ 'component' => $component,
+ 'filearea' => $filearea,
+ 'itemid' => 0,
+ 'filepath' => '/',
+ 'filename' => $filename,
+ ];
+ // Load the h5p file into DB.
+ $fs = get_file_storage();
+ if (!$fs->get_file($contextid, $component, $filearea, $filerecord['itemid'], $filerecord['filepath'], $filename)) {
+ $fs->create_file_from_pathname($filerecord, $path);
+ }
+
+ // Make the URL to pass to the player.
+ if ($typeurl == self::WSPLUGINFILE) {
+ $url = \moodle_url::make_webservice_pluginfile_url(
+ $filerecord['contextid'],
+ $filerecord['component'],
+ $filerecord['filearea'],
+ $filerecord['itemid'],
+ $filerecord['filepath'],
+ $filerecord['filename']
+ );
+ } else {
+ $includetoken = false;
+ if ($typeurl == self::TOKENPLUGINFILE) {
+ $includetoken = true;
+ }
+ $url = \moodle_url::make_pluginfile_url(
+ $filerecord['contextid'],
+ $filerecord['component'],
+ $filerecord['filearea'],
+ $filerecord['itemid'],
+ $filerecord['filepath'],
+ $filerecord['filename'],
+ false,
+ $includetoken
+ );
+ }
+
+ $config = new stdClass();
+ $h5pplayer = new player($url->out(false), $config);
+ // We need to add assets to page to create the export file.
+ $h5pplayer->add_assets_to_page();
+
+ // Call the method. We need the id of the new H5P content.
+ $rc = new \ReflectionClass(player::class);
+ $rcp = $rc->getProperty('h5pid');
+ $rcp->setAccessible(true);
+ $h5pid = $rcp->getValue($h5pplayer);
+
+ // Get the info export file.
+ $factory = new factory();
+ $core = $factory->get_core();
+ $content = $core->loadContent($h5pid);
+ $slug = $content['slug'] ? $content['slug'] . '-' : '';
+ $exportfilename = "{$slug}{$h5pid}.h5p";
+ $fileh5p = $core->fs->get_export_file($exportfilename);
+ $deployedfile = [];
+ $deployedfile['filename'] = $fileh5p->get_filename();
+ $deployedfile['filepath'] = $fileh5p->get_filepath();
+ $deployedfile['mimetype'] = $fileh5p->get_mimetype();
+ $deployedfile['filesize'] = $fileh5p->get_filesize();
+ $deployedfile['timemodified'] = $fileh5p->get_timemodified();
+
+ // Create the url depending the request was made through typeurl.
+ if ($typeurl == self::WSPLUGINFILE) {
+ $url = \moodle_url::make_webservice_pluginfile_url(
+ $fileh5p->get_contextid(),
+ $fileh5p->get_component(),
+ $fileh5p->get_filearea(),
+ '',
+ '',
+ $fileh5p->get_filename()
+ );
+ } else {
+ $includetoken = false;
+ if ($typeurl == self::TOKENPLUGINFILE) {
+ $includetoken = true;
+ }
+ $url = \moodle_url::make_pluginfile_url(
+ $fileh5p->get_contextid(),
+ $fileh5p->get_component(),
+ $fileh5p->get_filearea(),
+ '',
+ '',
+ $fileh5p->get_filename(),
+ false,
+ $includetoken
+ );
+ }
+ $deployedfile['fileurl'] = $url->out(false);
+
+ return $deployedfile;
+ }
}
$this->assertCount(7, $messages->error);
$this->assertCount(2, $messages->info);
}
+
+ /**
+ * Test the behaviour of get_export_info().
+ */
+ public function test_get_export_info(): void {
+ $this->resetAfterTest();
+
+ $filename = 'guess-the-answer.h5p';
+ $syscontext = \context_system::instance();
+
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
+ $deployedfile = $generator->create_export_file($filename,
+ $syscontext->id,
+ file_storage::COMPONENT,
+ file_storage::EXPORT_FILEAREA);
+
+ // Test scenario 1: Get export information from correct filename.
+ $helperfile = helper::get_export_info($deployedfile['filename']);
+ $this->assertEquals($deployedfile['filename'], $helperfile['filename']);
+ $this->assertEquals($deployedfile['filepath'], $helperfile['filepath']);
+ $this->assertEquals($deployedfile['filesize'], $helperfile['filesize']);
+ $this->assertEquals($deployedfile['timemodified'], $helperfile['timemodified']);
+ $this->assertEquals($deployedfile['fileurl'], $helperfile['fileurl']);
+
+ // Test scenario 2: Get export information from correct filename and url.
+ $url = \moodle_url::make_pluginfile_url(
+ $syscontext->id,
+ file_storage::COMPONENT,
+ 'unittest',
+ 0,
+ '/',
+ $deployedfile['filename'],
+ false,
+ true
+ );
+ $helperfile = helper::get_export_info($deployedfile['filename'], $url);
+ $this->assertEquals($url, $helperfile['fileurl']);
+
+ // Test scenario 3: Get export information from correct filename and factory.
+ $factory = new \core_h5p\factory();
+ $helperfile = helper::get_export_info($deployedfile['filename'], null, $factory);
+ $this->assertEquals($deployedfile['filename'], $helperfile['filename']);
+ $this->assertEquals($deployedfile['filepath'], $helperfile['filepath']);
+ $this->assertEquals($deployedfile['filesize'], $helperfile['filesize']);
+ $this->assertEquals($deployedfile['timemodified'], $helperfile['timemodified']);
+ $this->assertEquals($deployedfile['fileurl'], $helperfile['fileurl']);
+
+ // Test scenario 4: Get export information from wrong filename.
+ $helperfile = helper::get_export_info('nofileexist.h5p', $url);
+ $this->assertNull($helperfile);
+ }
}
$string['autologinguests'] = 'Auto-login guests';
$string['searchareas'] = 'Search areas';
$string['availableto'] = 'Available to';
-$string['availablelicenses'] = 'Available licences';
$string['backgroundcolour'] = 'Transparent colour';
$string['backups'] = 'Backups';
$string['backup_shortname'] = 'Use course name in backup filename';
$string['manageformats'] = 'Manage course formats';
$string['manageformatsgotosettings'] = 'Default format can be changed in {$a}';
$string['managelang'] = 'Manage';
-$string['managelicenses'] = 'Manage licences';
$string['manageqbehaviours'] = 'Manage question behaviours';
$string['manageqtypes'] = 'Manage question types';
$string['maturity50'] = 'Alpha';
$string['registrationwarning'] = 'Your site is not yet registered.';
$string['registrationwarningcontactadmin'] = 'Your site is not yet registered. Please notify your administrator.';
$string['releasenoteslink'] = 'For information about this version of Moodle, please see the online <a target="_blank" href="{$a}">Release Notes</a>';
+$string['rememberuserlicensepref'] = 'Remember user licence preference';
+$string['rememberuserlicensepref_help'] = 'If enabled, the last licence selected by the user is preselected when uploading a file in the file picker. Otherwise, the default site licence is preselected.';
$string['rememberusername'] = 'Remember username';
$string['rememberusername_desc'] = 'Enable if you want to store permanent cookies with usernames during user login. Permanent cookies may be considered a privacy issue if used without consent.';
$string['reportsmanage'] = 'Manage reports';
$string['registerwithmoodleorg'] = 'Register your site';
$string['configrequestcategoryselection'] = 'Allow the selection of a category when requesting a course.';
$string['requestcategoryselection'] = 'Enable category selection';
+
+// Deprecated since Moodle 3.9.
+$string['availablelicenses'] = 'Available licences';
+$string['managelicenses'] = 'Manage licences';
$string['defaultissuername'] = 'Badge issuer name';
$string['defaultissuername_desc'] = 'Name of the issuing agent or authority.';
$string['delbadge'] = 'Would you like to delete badge \'{$a}\' and remove all existing issued badges?';
+$string['delexternalbackpack'] = 'Delete site backpack';
$string['delexternalbackpackconfirm'] = 'Delete site backpack \'{$a}\'?';
$string['delconfirm'] = 'Delete and remove existing issued badges';
$string['deletehelp'] = '<p>Fully deleting a badge means that all its information and criteria records will be permanently removed. Users who have earned this badge will no longer be able to access it and display it on their profile pages.</p>
$string['description'] = 'Description';
$string['disconnect'] = 'Disconnect';
$string['donotaward'] = 'Currently, this badge is not active, so it cannot be awarded to users. If you would like to award this badge, please set its status to active.';
-$string['editsettings'] = 'Edit settings';
$string['enablebadges'] = 'Enable badges';
$string['endorsement'] = 'Endorsement';
$string['error:backpackdatainvalid'] = 'The data return from the backpack was invalid.';
$string['mybadges'] = 'My badges';
$string['mybackpack'] = 'My backpack settings';
$string['never'] = 'Never';
+$string['newbackpack'] = 'Add a new backpack';
$string['newbadge'] = 'Add a new badge';
$string['newimage'] = 'New image';
$string['noalignment'] = 'This badge does not have any external skills or standards specified.';
$string['setup'] = 'Set up connection';
$string['sitebackpack'] = 'Active external backpack';
$string['sitebackpack_help'] = 'The external backpack that users can connect to from this site. Note that changing this setting after users have connected their backpacks will require each user to go to their backpack settings page and disconnect then reconnect.';
+$string['sitebackpackdeleted'] = 'The site backpack has been deleted.';
+$string['sitebackpacknotdeleted'] = 'This backpack couldn\'t be deleted because it\'s currently the site default.';
$string['sitebackpackverify'] = 'Backpack connection';
$string['sitebackpackwarning'] = 'Could not connect to backpack. <br/><br/>Check that the "Badge issuer email address" admin setting is the valid email for an account on the backpack website. <br/><br/>Check that the "Badge issuer password" on the <a href="{$a->url}">site backpack settings page</a>, is the correct password for the account on the backpack website. <br/><br/>The backpack returned: "{$a->warning}"';
$string['sitebadges'] = 'Site badges';
$string['error:nogroups'] = '<p>There are no public collections of badges available in your backpack. </p> <p>Only public collections are shown. <a href="https://backpack.openbadges.org">Visit your backpack</a> to create some public collections.</p>';
$string['nobackpackbadges'] = 'There are no badges in the collections you have selected. <a href="mybackpack.php">Add more collections</a>.';
$string['nobackpackcollections'] = 'No badge collections have been selected. <a href="mybackpack.php">Add collections</a>.';
+
+// Deprecated since Moodle 3.9.
+$string['editsettings'] = 'Edit settings';
$string['cachedef_h5p_content_type_translations'] = 'H5P content-type libraries translations';
$string['cachedef_htmlpurifier'] = 'HTML Purifier - cleaned content';
$string['cachedef_langmenu'] = 'List of available languages';
+$string['cachedef_license'] = 'List of licences';
$string['cachedef_message_time_last_message_between_users'] = 'Time created for most recent message in a conversation';
$string['cachedef_modelfirstanalyses'] = 'First analysis by model and analysable';
$string['cachedef_locking'] = 'Locking';
$string['cachedef_repositories'] = 'Repositories instances data';
$string['cachedef_roledefs'] = 'Role definitions';
$string['cachedef_grade_categories'] = 'Grade category queries';
+$string['cachedef_grade_letters'] = 'Grade letters queries';
$string['cachedef_string'] = 'Language string cache';
$string['cachedef_tags'] = 'Tags collections and areas';
$string['cachedef_temp_tables'] = 'Temporary tables cache';
pacific/ponape,core_timezones
pacific/truk,core_timezones
pacific/yap,core_timezones
+editsettings,core_badges
+availablelicenses,core_admin
+managelicenses,core_admin
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+// Core licenses.
$string['allrightsreserved'] = 'All rights reserved';
$string['cc'] = 'Creative Commons';
$string['cc-nc'] = 'Creative Commons - No Commercial';
$string['cc-nd'] = 'Creative Commons - NoDerivs';
$string['cc-sa'] = 'Creative Commons - ShareAlike';
$string['public'] = 'Public domain';
-$string['unknown'] = 'Other';
+$string['unknown'] = 'Licence not specified';
+
+// Error messages.
+$string['cannotdeletecore'] = 'Cannot delete a standard licence';
+$string['cannotdeletelicenseinuse'] = 'Cannot delete a licence which is currently assigned to one or more files';
+$string['licensenotfoundshortname'] = 'Cannot find a licence with the short name \'{$a}\'';
+$string['missinglicensesortorder'] = 'Cannot set licence order, one or more installed licences is missing from new order';
$string['download'] = 'Download';
$string['downloadallfiles'] = 'Download all files';
$string['downloadfolder'] = 'Download all';
-$string['downloadselected'] = 'Download selected files';
-$string['deleteselected'] = 'Delete selected';
$string['downloadsucc'] = 'The file has been downloaded successfully';
$string['draftareanofiles'] = 'Cannot be downloaded because there is no files attached';
$string['editrepositoryinstance'] = 'Edit repository instance';
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+$string['addcondition'] = 'Add condition';
+$string['adverbfor_and'] = 'and';
+$string['adverbfor_andnot'] = 'and';
+$string['adverbfor_or'] = 'or';
+$string['applyfilters'] = 'Apply filters';
+$string['clearfilterrow'] = 'Remove filter row';
+$string['clearfilters'] = 'Clear filters';
$string['countparticipantsfound'] = '{$a} participants found';
+$string['filtersetmatchdescription'] = 'How multiple filters should be combined';
+$string['match'] = 'Match';
+$string['matchofthefollowing'] = 'of the following:';
+$string['placeholdertypeorselect'] = 'Type or select...';
+$string['placeholdertype'] = 'Type...';
$string['privacy:courserequestpath'] = 'Requested courses';
$string['privacy:descriptionpath'] = 'Profile description';
$string['privacy:devicespath'] = 'User devices';
$string['privacy:profileimagespath'] = 'Profile images';
$string['privacy:privatefilespath'] = 'Private files';
$string['privacy:sessionpath'] = 'Session data';
+$string['filterbykeyword'] = 'Keyword';
+$string['selectfiltertype'] = 'Select';
$string['target:upcomingactivitiesdue'] = 'Upcoming activities due';
$string['target:upcomingactivitiesdue_help'] = 'This target generates reminders for upcoming activities due.';
$string['target:upcomingactivitiesdueinfo'] = 'All upcoming activities due insights are listed here. These students have received these insights directly.';
* Special class for license administration.
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @deprecated since Moodle 3.9 MDL-45184. Please use \tool_licensemanager\manager instead.
+ * @todo MDL-45184 This class will be deleted in Moodle 4.3.
*/
class admin_setting_managelicenses extends admin_setting {
/**
- * Calls parent::__construct with specific arguments
+ * @deprecated since Moodle 3.9 MDL-45184. Please use \tool_licensemanager\manager instead.
+ * @todo MDL-45184 This class will be deleted in Moodle 4.3
*/
public function __construct() {
- $this->nosave = true;
- parent::__construct('licensesui', get_string('licensesettings', 'admin'), '', '');
+ global $ADMIN;
+
+ debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
+ DEBUG_DEVELOPER);
+
+ // Replace admin setting load with new external page load for tool_licensemanager, if not loaded already.
+ if (!is_null($ADMIN->locate('licensemanager'))) {
+ $temp = new admin_externalpage('licensemanager',
+ get_string('licensemanager', 'tool_licensemanager'),
+ \tool_licensemanager\helper::get_licensemanager_url());
+
+ $ADMIN->add('license', $temp);
+ }
}
/**
* Always returns true, does nothing
*
+ * @deprecated since Moodle 3.9 MDL-45184.
+ * @todo MDL-45184 This method will be deleted in Moodle 4.3
+ *
* @return true
*/
public function get_setting() {
+ debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
+ DEBUG_DEVELOPER);
+
return true;
}
/**
* Always returns true, does nothing
*
+ * @deprecated since Moodle 3.9 MDL-45184.
+ * @todo MDL-45184 This method will be deleted in Moodle 4.3
+ *
* @return true
*/
public function get_defaultsetting() {
+ debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
+ DEBUG_DEVELOPER);
+
return true;
}
/**
* Always returns '', does not write anything
*
+ * @deprecated since Moodle 3.9 MDL-45184.
+ * @todo MDL-45184 This method will be deleted in Moodle 4.3
+ *
* @return string Always returns ''
*/
public function write_setting($data) {
+ debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
+ DEBUG_DEVELOPER);
+
// do not write any setting
return '';
}
/**
* Builds the XHTML to display the control
*
+ * @deprecated since Moodle 3.9 MDL-45184. Please use \tool_licensemanager\manager instead.
+ * @todo MDL-45184 This method will be deleted in Moodle 4.3
+ *
* @param string $data Unused
* @param string $query
* @return string
*/
public function output_html($data, $query='') {
- global $CFG, $OUTPUT;
- require_once($CFG->libdir . '/licenselib.php');
- $url = "licenses.php?sesskey=" . sesskey();
-
- // display strings
- $txt = get_strings(array('administration', 'settings', 'name', 'enable', 'disable', 'none'));
- $licenses = license_manager::get_licenses();
-
- $return = $OUTPUT->heading(get_string('availablelicenses', 'admin'), 3, 'main', true);
-
- $return .= $OUTPUT->box_start('generalbox editorsui');
-
- $table = new html_table();
- $table->head = array($txt->name, $txt->enable);
- $table->colclasses = array('leftalign', 'centeralign');
- $table->id = 'availablelicenses';
- $table->attributes['class'] = 'admintable generaltable';
- $table->data = array();
-
- foreach ($licenses as $value) {
- $displayname = html_writer::link($value->source, get_string($value->shortname, 'license'), array('target'=>'_blank'));
-
- if ($value->enabled == 1) {
- $hideshow = html_writer::link($url.'&action=disable&license='.$value->shortname,
- $OUTPUT->pix_icon('t/hide', get_string('disable')));
- } else {
- $hideshow = html_writer::link($url.'&action=enable&license='.$value->shortname,
- $OUTPUT->pix_icon('t/show', get_string('enable')));
- }
-
- if ($value->shortname == $CFG->sitedefaultlicense) {
- $displayname .= ' '.$OUTPUT->pix_icon('t/locked', get_string('default'));
- $hideshow = '';
- }
-
- $enabled = true;
+ debugging('admin_setting_managelicenses class is deprecated. Please use \tool_licensemanager\manager instead.',
+ DEBUG_DEVELOPER);
- $table->data[] =array($displayname, $hideshow);
- }
- $return .= html_writer::table($table);
- $return .= $OUTPUT->box_end();
- return highlight($query, $return);
+ redirect(\tool_licensemanager\helper::get_licensemanager_url());
}
}
});
var context = $.extend({items: items}, options, state);
// Render the template.
- return templates.render('core/form_autocomplete_selection_items', context)
+ return templates.render(options.templates.items, context)
.then(function(html, js) {
// Add it to the page.
templates.replaceNodeContents(newSelection, html, js);
* @param {Boolean} showSuggestions - If suggestions should be shown
* @param {String} noSelectionString - Text to display when there is no selection
* @param {Boolean} closeSuggestionsOnSelect - Whether to close the suggestions immediately after making a selection.
+ * @param {Object} templateOverrides A set of templates to use instead of the standard templates
* @return {Promise}
*/
enhance: function(selector, tags, ajax, placeholder, caseSensitive, showSuggestions, noSelectionString,
- closeSuggestionsOnSelect) {
+ closeSuggestionsOnSelect, templateOverrides) {
// Set some default values.
var options = {
selector: selector,
placeholder: placeholder,
caseSensitive: false,
showSuggestions: true,
- noSelectionString: noSelectionString
+ noSelectionString: noSelectionString,
+ templates: $.extend({
+ input: 'core/form_autocomplete_input',
+ items: 'core/form_autocomplete_selection_items',
+ layout: 'core/form_autocomplete_layout',
+ selection: 'core/form_autocomplete_selection',
+ suggestions: 'core/form_autocomplete_suggestions',
+ }, templateOverrides),
};
var pendingKey = 'autocomplete-setup-' + selector;
M.util.js_pending(pendingKey);
// Collect rendered inline JS to be executed once the HTML is shown.
var collectedjs = '';
- var renderInput = templates.render('core/form_autocomplete_input', context).then(function(html, js) {
+ var renderLayout = templates.render(options.templates.layout, {})
+ .then(function(html) {
+ return $(html);
+ });
+
+ var renderInput = templates.render(options.templates.input, context).then(function(html, js) {
collectedjs += js;
- return html;
+ return $(html);
});
- var renderDatalist = templates.render('core/form_autocomplete_suggestions', context).then(function(html, js) {
+ var renderDatalist = templates.render(options.templates.suggestions, context).then(function(html, js) {
collectedjs += js;
- return html;
+ return $(html);
});
- var renderSelection = templates.render('core/form_autocomplete_selection', context).then(function(html, js) {
+ var renderSelection = templates.render(options.templates.selection, context).then(function(html, js) {
collectedjs += js;
- return html;
+ return $(html);
});
- return $.when(renderInput, renderDatalist, renderSelection)
- .then(function(input, suggestions, selection) {
+ return $.when(renderLayout, renderInput, renderDatalist, renderSelection)
+ .then(function(layout, input, suggestions, selection) {
originalSelect.hide();
- originalSelect.after(suggestions);
- originalSelect.after(input);
- originalSelect.after(selection);
+ var container = originalSelect.parent();
+
+ container.append(layout);
+ container.find('[data-region="form_autocomplete-input"]').replaceWith(input);
+ container.find('[data-region="form_autocomplete-suggestions"]').replaceWith(suggestions);
+ container.find('[data-region="form_autocomplete-selection"]').replaceWith(selection);
templates.runTemplateJS(collectedjs);
*
* @param {Number} contextId
* @param {Array} notificationList
+ * @param {Boolean} userLoggedIn
*/
-export const init = (contextId, notificationList) => {
+export const init = (contextId, notificationList, userLoggedIn) => {
currentContextId = contextId;
// Setup the message target region if it isn't setup already
// Add provided notifications.
addNotifications(notificationList);
- // Perform an initial poll for any new notifications.
- fetchNotifications();
+ // If the user is not logged in then we can not fetch anything for them.
+ if (userLoggedIn) {
+ // Perform an initial poll for any new notifications.
+ fetchNotifications();
+ }
};
// To maintain backwards compatability we export default here.
e.preventDefault(); // This will prevent default error dialogue.
&nb