MDL-45184 tool_licenses: Add custom licenses
authorTom Dickman <tomdickman@catalyst-au.net>
Thu, 31 Oct 2019 01:32:17 +0000 (12:32 +1100)
committerTom Dickman <tomdickman@catalyst-au.net>
Tue, 26 May 2020 02:08:05 +0000 (12:08 +1000)
This feature adds an admin tool for creating custom licenses.
Now custom licenses can be added and amended in Moodle, and the site
default can be set to a custom license.

Core licenses remain hard-coded and are uneditable, so they will always
require update within Moodle core updates, and maintain their
internationalisation through core language strings.

This also includes fundamental changes to the license API including
the addition of license caching and deprecation of no longer required
admin settings for license management.

39 files changed:
admin/settings/license.php [new file with mode: 0644]
admin/settings/plugins.php
admin/settings/top.php
admin/tool/licensemanager/amd/build/delete_license.min.js [new file with mode: 0644]
admin/tool/licensemanager/amd/build/delete_license.min.js.map [new file with mode: 0644]
admin/tool/licensemanager/amd/src/delete_license.js [new file with mode: 0644]
admin/tool/licensemanager/classes/form/edit_license.php [new file with mode: 0644]
admin/tool/licensemanager/classes/helper.php [new file with mode: 0644]
admin/tool/licensemanager/classes/manager.php [new file with mode: 0644]
admin/tool/licensemanager/classes/output/renderer.php [new file with mode: 0644]
admin/tool/licensemanager/classes/output/table.php [new file with mode: 0644]
admin/tool/licensemanager/classes/privacy/provider.php [new file with mode: 0644]
admin/tool/licensemanager/index.php [moved from admin/licenses.php with 55% similarity]
admin/tool/licensemanager/lang/en/tool_licensemanager.php [new file with mode: 0644]
admin/tool/licensemanager/settings.php [new file with mode: 0644]
admin/tool/licensemanager/tests/behat/delete_license.feature [new file with mode: 0644]
admin/tool/licensemanager/tests/behat/edit_license.feature [new file with mode: 0644]
admin/tool/licensemanager/tests/behat/license_manager.feature [new file with mode: 0644]
admin/tool/licensemanager/tests/helper_test.php [new file with mode: 0644]
admin/tool/licensemanager/tests/manager_test.php [new file with mode: 0644]
admin/tool/licensemanager/version.php [new file with mode: 0644]
lang/en/admin.php
lang/en/cache.php
lang/en/deprecated.txt
lang/en/license.php
lib/adminlib.php
lib/classes/plugin_manager.php
lib/db/caches.php
lib/db/install.xml
lib/db/upgrade.php
lib/db/upgradelib.php
lib/form/filemanager.js
lib/form/filemanager.php
lib/licenselib.php
lib/tests/licenselib_test.php [new file with mode: 0644]
lib/tests/upgradelib_test.php
repository/filepicker.js
repository/lib.php
version.php

diff --git a/admin/settings/license.php b/admin/settings/license.php
new file mode 100644 (file)
index 0000000..bd9ad8f
--- /dev/null
@@ -0,0 +1,45 @@
+<?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);
+}
index 278d4f3..4eb6ec4 100644 (file)
@@ -182,20 +182,6 @@ if ($hassiteconfig) {
         $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')));
 
index 8c922d1..8717843 100644 (file)
@@ -32,6 +32,7 @@ $ADMIN->add('root', new admin_category('analytics', new lang_string('analytics',
 $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')));
diff --git a/admin/tool/licensemanager/amd/build/delete_license.min.js b/admin/tool/licensemanager/amd/build/delete_license.min.js
new file mode 100644 (file)
index 0000000..17dd2af
Binary files /dev/null and b/admin/tool/licensemanager/amd/build/delete_license.min.js differ
diff --git a/admin/tool/licensemanager/amd/build/delete_license.min.js.map b/admin/tool/licensemanager/amd/build/delete_license.min.js.map
new file mode 100644 (file)
index 0000000..caee9ae
Binary files /dev/null and b/admin/tool/licensemanager/amd/build/delete_license.min.js.map differ
diff --git a/admin/tool/licensemanager/amd/src/delete_license.js b/admin/tool/licensemanager/amd/src/delete_license.js
new file mode 100644 (file)
index 0000000..6abdcec
--- /dev/null
@@ -0,0 +1,51 @@
+// 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;
+                });
+            });
+    });
diff --git a/admin/tool/licensemanager/classes/form/edit_license.php b/admin/tool/licensemanager/classes/form/edit_license.php
new file mode 100644 (file)
index 0000000..624ce80
--- /dev/null
@@ -0,0 +1,124 @@
+<?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;
+    }
+}
diff --git a/admin/tool/licensemanager/classes/helper.php b/admin/tool/licensemanager/classes/helper.php
new file mode 100644 (file)
index 0000000..b71a9aa
--- /dev/null
@@ -0,0 +1,154 @@
+<?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;
+    }
+}
diff --git a/admin/tool/licensemanager/classes/manager.php b/admin/tool/licensemanager/classes/manager.php
new file mode 100644 (file)
index 0000000..2f3ad2c
--- /dev/null
@@ -0,0 +1,247 @@
+<?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;
+    }
+}
diff --git a/admin/tool/licensemanager/classes/output/renderer.php b/admin/tool/licensemanager/classes/output/renderer.php
new file mode 100644 (file)
index 0000000..19ac8d4
--- /dev/null
@@ -0,0 +1,96 @@
+<?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;
+    }
+}
diff --git a/admin/tool/licensemanager/classes/output/table.php b/admin/tool/licensemanager/classes/output/table.php
new file mode 100644 (file)
index 0000000..ff01ae9
--- /dev/null
@@ -0,0 +1,186 @@
+<?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';
+
+        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]);
+            } else {
+                $deletelicense = '';
+            }
+        }
+        $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 .= '&nbsp;'.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;
+    }
+}
diff --git a/admin/tool/licensemanager/classes/privacy/provider.php b/admin/tool/licensemanager/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..b4eb611
--- /dev/null
@@ -0,0 +1,47 @@
+<?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';
+    }
+}
similarity index 55%
rename from admin/licenses.php
rename to admin/tool/licensemanager/index.php
index 820e775..123dd9d 100644 (file)
@@ -1,5 +1,4 @@
 <?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);
diff --git a/admin/tool/licensemanager/lang/en/tool_licensemanager.php b/admin/tool/licensemanager/lang/en/tool_licensemanager.php
new file mode 100644 (file)
index 0000000..97e372a
--- /dev/null
@@ -0,0 +1,52 @@
+<?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.';
+
diff --git a/admin/tool/licensemanager/settings.php b/admin/tool/licensemanager/settings.php
new file mode 100644 (file)
index 0000000..ffb61e7
--- /dev/null
@@ -0,0 +1,33 @@
+<?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);
+}
diff --git a/admin/tool/licensemanager/tests/behat/delete_license.feature b/admin/tool/licensemanager/tests/behat/delete_license.feature
new file mode 100644 (file)
index 0000000..f5405ec
--- /dev/null
@@ -0,0 +1,29 @@
+@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"
+    And I click on "Save changes" "button" in the "Delete licence" "dialogue"
+    Then I should not see "MIT Licence" in the "manage-licenses" "table"
+    And I log out
+
+  Scenario: I cannot delete a standard Moodle 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"
diff --git a/admin/tool/licensemanager/tests/behat/edit_license.feature b/admin/tool/licensemanager/tests/behat/edit_license.feature
new file mode 100644 (file)
index 0000000..d8dbfb3
--- /dev/null
@@ -0,0 +1,83 @@
+@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                                |
+    And 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"
+    And I log out
+
+  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                                |
+    And 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"
+    Then 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"
+    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"
+    And I log out
+
+  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                                |
+    And I press "Save changes"
+    Then I should see "Licence manager"
+    And I should see "2019030100" in the "MIT" "table_row"
+    And I log out
+
+  @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"
+    And I click on "Edit" "icon" in the "MIT" "table_row"
+    Then I should see "Edit licence"
+    And the "shortname" "field" should be disabled
+    And I log out
diff --git a/admin/tool/licensemanager/tests/behat/license_manager.feature b/admin/tool/licensemanager/tests/behat/license_manager.feature
new file mode 100644 (file)
index 0000000..eae3cc1
--- /dev/null
@@ -0,0 +1,38 @@
+@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"
+    And I log out
+
+  @javascript
+  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 "Default" "icon" should exist in the "public" "table_row"
+    And "Enable" "icon" should not exist in the "public" "table_row"
+    And "Default" "icon" should not exist in the "cc" "table_row"
+    When 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
+    Then "Default" "icon" should exist in the "cc" "table_row"
+    And "Enable" "icon" should not exist in the "cc" "table_row"
+    And "Default" "icon" should not exist in the "public" "table_row"
+    And I log out
diff --git a/admin/tool/licensemanager/tests/helper_test.php b/admin/tool/licensemanager/tests/helper_test.php
new file mode 100644 (file)
index 0000000..14c34c6
--- /dev/null
@@ -0,0 +1,44 @@
+<?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));
+    }
+}
diff --git a/admin/tool/licensemanager/tests/manager_test.php b/admin/tool/licensemanager/tests/manager_test.php
new file mode 100644 (file)
index 0000000..e86cf68
--- /dev/null
@@ -0,0 +1,199 @@
+<?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);
+    }
+
+}
diff --git a/admin/tool/licensemanager/version.php b/admin/tool/licensemanager/version.php
new file mode 100644 (file)
index 0000000..faf9c51
--- /dev/null
@@ -0,0 +1,31 @@
+<?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;
index 08ccfaa..19c40d7 100644 (file)
@@ -81,7 +81,6 @@ $string['autolang'] = 'Language autodetect';
 $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';
@@ -763,7 +762,6 @@ $string['managecustomfields'] = 'Manage custom field types';
 $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';
@@ -1046,6 +1044,8 @@ $string['registration_help'] = 'By registering:
 $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';
@@ -1461,3 +1461,7 @@ $string['registermoodleorgli2'] = 'Statistics about your site will be added to t
 $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';
index cb59517..30dcdc9 100644 (file)
@@ -60,6 +60,7 @@ $string['cachedef_groupdata'] = 'Course group information';
 $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';
index 280b3bd..e8608e7 100644 (file)
@@ -141,3 +141,5 @@ europe/belfast,core_timezones
 pacific/ponape,core_timezones
 pacific/truk,core_timezones
 pacific/yap,core_timezones
+availablelicenses,core_admin
+managelicenses,core_admin
index 1c40f78..f33e687 100644 (file)
@@ -22,6 +22,7 @@
  * @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';
@@ -30,4 +31,10 @@ $string['cc-nc-sa'] = 'Creative Commons - No Commercial ShareAlike';
 $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';
index b75e4e1..1541db2 100644 (file)
@@ -7243,40 +7243,72 @@ class admin_setting_manageantiviruses extends admin_setting {
  * 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 '';
     }
@@ -7284,53 +7316,18 @@ class admin_setting_managelicenses extends admin_setting {
     /**
      * 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());
     }
 }
 
index ba448c9..0d8d832 100644 (file)
@@ -1998,10 +1998,11 @@ class core_plugin_manager {
 
             'tool' => array(
                 'analytics', 'availabilityconditions', 'behat', 'capability', 'cohortroles', 'customlang',
-                'dataprivacy', 'dbtransfer', 'filetypes', 'generator', 'health', 'httpsreplace', 'innodb', 'installaddon',
-                'langimport', 'log', 'lp', 'lpimportcsv', 'lpmigrate', 'messageinbound', 'mobile', 'multilangupgrade',
-                'monitor', 'oauth2', 'phpunit', 'policy', 'profiling', 'recyclebin', 'replace', 'spamcleaner', 'task',
-                'templatelibrary', 'uploadcourse', 'uploaduser', 'unsuproles', 'usertours', 'xmldb'
+                'dataprivacy', 'dbtransfer', 'filetypes', 'generator', 'health', 'httpsreplace', 'innodb',
+                'installaddon', 'langimport', 'licensemanager', 'log', 'lp', 'lpimportcsv', 'lpmigrate', 'messageinbound',
+                'mobile', 'multilangupgrade', 'monitor', 'oauth2', 'phpunit', 'policy', 'profiling', 'recyclebin',
+                'replace', 'spamcleaner', 'task', 'templatelibrary', 'uploadcourse', 'uploaduser', 'unsuproles',
+                'usertours', 'xmldb'
             ),
 
             'webservice' => array(
index a5bd37e..9cfc72d 100644 (file)
@@ -454,4 +454,11 @@ $definitions = array(
         'mode' => cache_store::MODE_APPLICATION,
         'simpledata' => true,
     ],
+
+    // Cache for licenses.
+    'license' => [
+        'mode' => cache_store::MODE_APPLICATION,
+        'simplekeys' => false,
+        'simpledata' => false
+    ],
 );
index 2bc9085..be3ac00 100644 (file)
         <INDEX NAME="component-filearea-contextid-itemid" UNIQUE="false" FIELDS="component, filearea, contextid, itemid"/>
         <INDEX NAME="contenthash" UNIQUE="false" FIELDS="contenthash"/>
         <INDEX NAME="pathnamehash" UNIQUE="true" FIELDS="pathnamehash"/>
+        <INDEX NAME="license" UNIQUE="false" FIELDS="license"/>
       </INDEXES>
     </TABLE>
     <TABLE NAME="files_reference" COMMENT="Store files references">
         <FIELD NAME="source" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
         <FIELD NAME="enabled" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
         <FIELD NAME="version" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+        <FIELD NAME="custom" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If this flag is set, license is custom and can be updated or deleted, otherwise license is a core license and cannot be edited."/>
+        <FIELD NAME="sortorder" TYPE="int" LENGTH="5" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
index 1f5796e..d0d822d 100644 (file)
@@ -2392,5 +2392,40 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2020052000.00);
     }
 
+    if ($oldversion < 2020052200.01) {
+
+        // Define field custom to be added to license.
+        $table = new xmldb_table('license');
+        $field = new xmldb_field('custom', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
+
+        // Conditionally launch add field custom.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Define field sortorder to be added to license.
+        $field = new xmldb_field('sortorder', XMLDB_TYPE_INTEGER, '5', null, XMLDB_NOTNULL, null, '0');
+
+        // Conditionally launch add field sortorder.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Define index license (not unique) to be added to files.
+        $table = new xmldb_table('files');
+        $index = new xmldb_index('license', XMLDB_INDEX_NOTUNIQUE, ['license']);
+
+        // Conditionally launch add index license.
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        // Upgrade the core license details.
+        upgrade_core_licenses();
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2020052200.01);
+    }
+
     return true;
 }
index d2ebc51..8e66a0f 100644 (file)
@@ -628,3 +628,124 @@ function upgrade_analytics_fix_contextids_defaults() {
     $params = ['zero' => '0', 'null' => 'null'];
     $DB->execute("UPDATE {analytics_models} set contextids = null WHERE " . $select, $params);
 }
+
+/**
+ * Upgrade core licenses shipped with Moodle.
+ */
+function upgrade_core_licenses() {
+    global $CFG, $DB;
+
+    $corelicenses = [];
+
+    $license = new stdClass();
+    $license->shortname = 'unknown';
+    $license->fullname = 'Licence not specified';
+    $license->source = '';
+    $license->enabled = 1;
+    $license->version = '2010033100';
+    $license->custom = 0;
+    $corelicenses[] = $license;
+
+    $license = new stdClass();
+    $license->shortname = 'allrightsreserved';
+    $license->fullname = 'All rights reserved';
+    $license->source = 'https://en.wikipedia.org/wiki/All_rights_reserved';
+    $license->enabled = 1;
+    $license->version = '2010033100';
+    $license->custom = 0;
+    $corelicenses[] = $license;
+
+    $license = new stdClass();
+    $license->shortname = 'public';
+    $license->fullname = 'Public domain';
+    $license->source = 'https://en.wikipedia.org/wiki/Public_domain';
+    $license->enabled = 1;
+    $license->version = '2010033100';
+    $license->custom = 0;
+    $corelicenses[] = $license;
+
+    $license = new stdClass();
+    $license->shortname = 'cc';
+    $license->fullname = 'Creative Commons';
+    $license->source = 'https://creativecommons.org/licenses/by/3.0/';
+    $license->enabled = 1;
+    $license->version = '2010033100';
+    $license->custom = 0;
+    $corelicenses[] = $license;
+
+    $license = new stdClass();
+    $license->shortname = 'cc-nd';
+    $license->fullname = 'Creative Commons - NoDerivs';
+    $license->source = 'https://creativecommons.org/licenses/by-nd/3.0/';
+    $license->enabled = 1;
+    $license->version = '2010033100';
+    $license->custom = 0;
+    $corelicenses[] = $license;
+
+    $license = new stdClass();
+    $license->shortname = 'cc-nc-nd';
+    $license->fullname = 'Creative Commons - No Commercial NoDerivs';
+    $license->source = 'https://creativecommons.org/licenses/by-nc-nd/3.0/';
+    $license->enabled = 1;
+    $license->version = '2010033100';
+    $license->custom = 0;
+    $corelicenses[] = $license;
+
+    $license = new stdClass();
+    $license->shortname = 'cc-nc';
+    $license->fullname = 'Creative Commons - No Commercial';
+    $license->source = 'https://creativecommons.org/licenses/by-nc/3.0/';
+    $license->enabled = 1;
+    $license->version = '2010033100';
+    $license->custom = 0;
+    $corelicenses[] = $license;
+
+    $license = new stdClass();
+    $license->shortname = 'cc-nc-sa';
+    $license->fullname = 'Creative Commons - No Commercial ShareAlike';
+    $license->source = 'https://creativecommons.org/licenses/by-nc-sa/3.0/';
+    $license->enabled = 1;
+    $license->version = '2010033100';
+    $license->custom = 0;
+    $corelicenses[] = $license;
+
+    $license = new stdClass();
+    $license->shortname = 'cc-sa';
+    $license->fullname = 'Creative Commons - ShareAlike';
+    $license->source = 'https://creativecommons.org/licenses/by-sa/3.0/';
+    $license->enabled = 1;
+    $license->version = '2010033100';
+    $license->custom = 0;
+    $corelicenses[] = $license;
+
+    foreach ($corelicenses as $corelicense) {
+        // Check for current license to maintain idempotence.
+        $currentlicense = $DB->get_record('license', ['shortname' => $corelicense->shortname]);
+        if (!empty($currentlicense)) {
+            $corelicense->id = $currentlicense->id;
+            // Remember if the license was enabled before upgrade.
+            $corelicense->enabled = $currentlicense->enabled;
+            $DB->update_record('license', $corelicense);
+        } else if (!isset($CFG->upgraderunning) || during_initial_install()) {
+            // Only install missing core licenses if not upgrading or during initial install.
+            $DB->insert_record('license', $corelicense);
+        }
+    }
+
+    // Add sortorder to all licenses.
+    $licenses = $DB->get_records('license');
+    $sortorder = 1;
+    foreach ($licenses as $license) {
+        $license->sortorder = $sortorder++;
+        $DB->update_record('license', $license);
+    }
+
+    // Set the license config values, used by file repository for rendering licenses at front end.
+    $activelicenses = $DB->get_records_menu('license', ['enabled' => 1], 'id', 'id, shortname');
+    set_config('licenses', implode(',', $activelicenses));
+
+    $sitedefaultlicense = get_config('', 'sitedefaultlicense');
+    if (empty($sitedefaultlicense) || !in_array($sitedefaultlicense, $activelicenses)) {
+        set_config('sitedefaultlicense', reset($activelicenses));
+    }
+}
index b3ebb6e..5fe7586 100644 (file)
@@ -704,17 +704,29 @@ M.form_filemanager.init = function(Y, options) {
             this.filemanager.one('.fp-content').fp_display_filelist(options, list, this.lazyloading);
             this.content_scrolled();
         },
-        populate_licenses_select: function(node) {
-            if (!node) {
+        populateLicensesSelect: function(licensenode, filenode) {
+            if (!licensenode) {
                 return;
             }
-            node.setContent('');
-            var licenses = this.options.licenses;
+            licensenode.setContent('');
+            var selectedlicense = this.filepicker_options.defaultlicense;
+            if (filenode) {
+                // File has a license already, use it.
+                selectedlicense = filenode.license;
+            } else if (this.filepicker_options.rememberuserlicensepref) {
+                selectedlicense = this.get_preference('recentlicense');
+            }
+            var licenses = this.filepicker_options.licenses;
             for (var i in licenses) {
-                var option = Y.Node.create('<option/>').
+                // Include the file's current license, even if not enabled, to prevent displaying
+                // misleading information about which license the file currently has assigned to it.
+                if (licenses[i].enabled == true || (filenode !== undefined && licenses[i].shortname === filenode.license)) {
+                    var option = Y.Node.create('<option/>').
+                    set('selected', (licenses[i].shortname == selectedlicense)).
                     set('value', licenses[i].shortname).
                     setContent(Y.Escape.html(licenses[i].fullname));
-                node.appendChild(option)
+                    licensenode.appendChild(option);
+                }
             }
         },
         set_current_tree: function(tree) {
@@ -888,7 +900,6 @@ M.form_filemanager.init = function(Y, options) {
             selectnode.all('.fp-saveas,.fp-path,.fp-author,.fp-license').each(function (node) {
                 node.all('label').set('for', node.one('input,select').generateID());
             });
-            this.populate_licenses_select(selectnode.one('.fp-license select'));
             // register event on clicking buttons
             selectnode.one('.fp-file-update').on('click', function(e) {
                 e.preventDefault();
@@ -1052,8 +1063,7 @@ M.form_filemanager.init = function(Y, options) {
             selectnode.one('.fp-saveas input').set('value', node.fullname);
             var foldername = this.get_parent_folder_name(node);
             selectnode.all('.fp-author input').set('value', node.author ? node.author : '');
-            selectnode.all('.fp-license select option[selected]').set('selected', false);
-            selectnode.all('.fp-license select option[value='+node.license+']').set('selected', true);
+            this.populateLicensesSelect(selectnode.one('.fp-license select'), node);
             selectnode.all('.fp-path select option[selected]').set('selected', false);
             selectnode.all('.fp-path select option').each(function(el){
                 if (el.get('value') == foldername) {
index 62670e5..8ec10b0 100644 (file)
@@ -396,6 +396,7 @@ class form_filemanager implements renderable {
     public function __construct(stdClass $options) {
         global $CFG, $USER, $PAGE;
         require_once($CFG->dirroot. '/repository/lib.php');
+        require_once($CFG->libdir . '/licenselib.php');
         $defaults = array(
             'maxbytes'=>-1,
             'areamaxbytes' => FILE_AREA_MAX_BYTES_UNLIMITED,
@@ -409,15 +410,9 @@ class form_filemanager implements renderable {
             'author'=>fullname($USER),
             'licenses'=>array()
             );
-        if (!empty($CFG->licenses)) {
-            $array = explode(',', $CFG->licenses);
-            foreach ($array as $license) {
-                $l = new stdClass();
-                $l->shortname = $license;
-                $l->fullname = get_string($license, 'license');
-                $defaults['licenses'][] = $l;
-            }
-        }
+
+        $defaults['licenses'] = license_manager::get_licenses();
+
         if (!empty($CFG->sitedefaultlicense)) {
             $defaults['defaultlicense'] = $CFG->sitedefaultlicense;
         }
index e493437..d1038b8 100644 (file)
 defined('MOODLE_INTERNAL') || die();
 
 class license_manager {
+
     /**
-     * Adding a new license type
+     * License is a core license and can not be updated or deleted.
+     */
+    const CORE_LICENSE = 0;
+
+    /**
+     * License is a custom license and can be updated and/or deleted.
+     */
+    const CUSTOM_LICENSE = 1;
+
+    /**
+     * Integer representation of boolean for a license that is enabled.
+     */
+    const LICENSE_ENABLED = 1;
+
+    /**
+     * Integer representation of boolean for a license that is disabled.
+     */
+    const LICENSE_DISABLED = 0;
+
+    /**
+     * Integer for moving a license up order.
+     */
+    const LICENSE_MOVE_UP = -1;
+
+    /**
+     * Integer for moving a license down order.
+     */
+    const LICENSE_MOVE_DOWN = 1;
+
+    /**
+     * Save a license record.
+     *
      * @param object $license {
      *            shortname => string a shortname of license, will be refered by files table[required]
      *            fullname  => string the fullname of the license [required]
@@ -39,52 +71,222 @@ class license_manager {
      *            version  => int a version number used by moodle [required]
      * }
      */
-    static public function add($license) {
-        global $DB;
-        if ($record = $DB->get_record('license', array('shortname'=>$license->shortname))) {
-            // record exists
-            if ($record->version < $license->version) {
-                // update license record
-                $license->enabled = $record->enabled;
-                $license->id = $record->id;
-                $DB->update_record('license', $license);
+    static public function save($license) {
+
+        $existinglicense = self::get_license_by_shortname($license->shortname);
+
+        if (!empty($existinglicense)) {
+            $id = $existinglicense->id;
+            if ($existinglicense->custom == self::CORE_LICENSE) {
+                // Can only update the enabled status and sortorder for core licenses.
+                $existinglicense->enabled = $license->enabled;
+                $existinglicense->sortorder = $license->sortorder;
+                $license = $existinglicense;
             }
+            $license->id = $id;
+            self::update($license);
         } else {
-            $DB->insert_record('license', $license);
+            self::create($license);
         }
+
         return true;
     }
 
     /**
-     * Get license records
-     * @param mixed $param
-     * @return array
+     * Adding a new license type
+     *
+     * @deprecated Since Moodle 3.9, MDL-45184.
+     * @todo MDL-67344 This will be deleted in Moodle 4.3.
+     * @see license_manager::save()
+     *
+     * @param object $license the license record to add.
+     *
+     * @return bool true on success.
+     */
+    public function add($license) : bool {
+        debugging('add() is deprecated. Please use license_manager::save() instead.', DEBUG_DEVELOPER);
+
+        return self::save($license);
+    }
+
+    /**
+     * Create a license record.
+     *
+     * @param object $license the license to create record for.
      */
-    static public function get_licenses($param = null) {
+    static protected function create($license) {
         global $DB;
-        if (empty($param) || !is_array($param)) {
-            $param = array();
+
+        $licensecount = count(self::get_licenses());
+        $license->sortorder = $licensecount + 1;
+        // Enable all created license by default.
+        $license->enabled = self::LICENSE_ENABLED;
+        // API can only create custom licenses, core licenses
+        // are directly created at install or upgrade.
+        $license->custom = self::CUSTOM_LICENSE;
+
+        $DB->insert_record('license', $license);
+        self::reset_license_cache();
+    }
+
+    /**
+     * Read licens record(s) from database.
+     *
+     * @param array $params license parameters to return licenses for.
+     *
+     * @return array $filteredlicenses object[] of licenses.
+     */
+    static public function read(array $params = []) {
+        $licenses = self::get_licenses();
+
+        $filteredlicenses = [];
+
+        foreach ($licenses as $shortname => $license) {
+            $filtermatch = true;
+            foreach ($params as $key => $value) {
+                if ($license->$key != $value) {
+                    $filtermatch = false;
+                }
+            }
+            if ($filtermatch) {
+                $filteredlicenses[$shortname] = $license;
+            }
         }
-        // get licenses by conditions
-        if ($records = $DB->get_records('license', $param)) {
-            return $records;
+        return $filteredlicenses;
+
+    }
+
+    /**
+     * Update a license record.
+     *
+     * @param object $license the license to update record for.
+     *
+     * @throws \moodle_exception if attempting to update a core license.
+     */
+    static protected function update($license) {
+        global $DB;
+
+        $DB->update_record('license', $license);
+        self::reset_license_cache();
+    }
+
+    /**
+     * Delete a custom license.
+     *
+     * @param string $licenseshortname the shortname of license.
+     *
+     * @throws \moodle_exception when attempting to delete a license you are not allowed to.
+     */
+    static public function delete($licenseshortname) {
+        global $DB;
+
+        $licensetodelete = self::get_license_by_shortname($licenseshortname);
+
+        if (!empty($licensetodelete)) {
+            if ($licensetodelete->custom == self::CUSTOM_LICENSE) {
+                // Check that the license is not in use by any files, if so it cannot be deleted.
+                $countfilesusinglicense = $DB->count_records('files', ['license' => $licenseshortname]);
+                if ($countfilesusinglicense > 0) {
+                    throw new moodle_exception('cannotdeletelicenseinuse', 'license');
+                }
+                $deletedsortorder = $licensetodelete->sortorder;
+                $DB->delete_records('license', ['id' => $licensetodelete->id]);
+
+                // We've deleted a license, so update our list of licenses so we don't save the deleted license again.
+                self::reset_license_cache();
+                $licenses = self::get_licenses();
+
+                foreach ($licenses as $license) {
+                    if ($license->sortorder > $deletedsortorder) {
+                        $license->sortorder = $license->sortorder - 1;
+                        self::save($license);
+                    }
+                }
+
+            } else {
+                throw new moodle_exception('cannotdeletecore', 'license');
+            }
         } else {
-            return array();
+            throw new moodle_exception('licensenotfoundshortname', 'license', $licenseshortname);
         }
     }
 
     /**
-     * Get license record by shortname
-     * @param mixed $param the shortname of license, or an array
-     * @return object
+     * Get license records.
+     *
+     * @return array|false object[] of license records of false if none.
      */
-    static public function get_license_by_shortname($name) {
+    static public function get_licenses() {
         global $DB;
-        if ($record = $DB->get_record('license', array('shortname'=>$name))) {
-            return $record;
+
+        $cache = \cache::make('core', 'license');
+        $licenses = $cache->get('licenses');
+
+        if (empty($licenses)) {
+            $licenses = [];
+            $records = $DB->get_records_select('license', null, null, 'sortorder ASC');
+            foreach ($records as $license) {
+                // Interpret core license strings for internationalisation.
+                if ($license->custom == self::CORE_LICENSE) {
+                    $license->fullname = get_string($license->shortname, 'license');
+                }
+                $licenses[$license->shortname] = $license;
+            }
+            $cache->set('licenses', $licenses);
+        }
+
+        return $licenses;
+    }
+
+    /**
+     * Change the sort order of a license (and it's sibling license as a result).
+     *
+     * @param int $direction value to change sortorder of license by.
+     * @param string $licenseshortname the shortname of license to changes sortorder for.
+     *
+     * @throws \moodle_exception if attempting to use invalid direction value.
+     */
+    static public function change_license_sortorder(int $direction, string $licenseshortname) : void {
+
+        if ($direction != self::LICENSE_MOVE_UP && $direction != self::LICENSE_MOVE_DOWN) {
+            throw new coding_exception(
+                'Must use a valid licence API move direction constant (LICENSE_MOVE_UP or LICENSE_MOVE_DOWN)');
+        }
+
+        $licenses = self::get_licenses();
+        $licensetoupdate = $licenses[$licenseshortname];
+
+        $currentsortorder = $licensetoupdate->sortorder;
+        $targetsortorder = $currentsortorder + $direction;
+
+        if ($targetsortorder > 0 && $targetsortorder <= count($licenses) ) {
+            foreach ($licenses as $license) {
+                if ($license->sortorder == $targetsortorder) {
+                    $license->sortorder = $license->sortorder - $direction;
+                    self::update($license);
+                }
+            }
+            $licensetoupdate->sortorder = $targetsortorder;
+            self::update($licensetoupdate);
+        }
+    }
+
+    /**
+     * Get license record by shortname
+     *
+     * @param string $name the shortname of license
+     * @return object|null the license or null if no license found.
+     */
+    static public function get_license_by_shortname(string $name) {
+        $licenses = self::read(['shortname' => $name]);
+
+        if (!empty($licenses)) {
+            $license = reset($licenses);
         } else {
-            return null;
+            $license = null;
         }
+
+        return $license;
     }
 
     /**
@@ -93,12 +295,12 @@ class license_manager {
      * @return boolean
      */
     static public function enable($license) {
-        global $DB;
         if ($license = self::get_license_by_shortname($license)) {
-            $license->enabled = 1;
-            $DB->update_record('license', $license);
+            $license->enabled = self::LICENSE_ENABLED;
+            self::update($license);
         }
         self::set_active_licenses();
+
         return true;
     }
 
@@ -108,25 +310,25 @@ class license_manager {
      * @return boolean
      */
     static public function disable($license) {
-        global $DB, $CFG;
+        global $CFG;
         // Site default license cannot be disabled!
         if ($license == $CFG->sitedefaultlicense) {
             print_error('error');
         }
         if ($license = self::get_license_by_shortname($license)) {
-            $license->enabled = 0;
-            $DB->update_record('license', $license);
+            $license->enabled = self::LICENSE_DISABLED;
+            self::update($license);
         }
         self::set_active_licenses();
+
         return true;
     }
 
     /**
-     * Store active licenses in global $CFG
+     * Store active licenses in global config.
      */
-    static private function set_active_licenses() {
-        // set to global $CFG
-        $licenses = self::get_licenses(array('enabled'=>1));
+    static protected function set_active_licenses() {
+        $licenses = self::read(['enabled' => self::LICENSE_ENABLED]);
         $result = array();
         foreach ($licenses as $l) {
             $result[] = $l->shortname;
@@ -135,85 +337,65 @@ class license_manager {
     }
 
     /**
-     * Install moodle build-in licenses
+     * Get the globally configured active licenses.
+     *
+     * @return array of license objects.
+     * @throws \coding_exception
+     */
+    static public function get_active_licenses() {
+        global $CFG;
+
+        $result = [];
+
+        if (!empty($CFG->licenses)) {
+            $activelicenses = explode(',', $CFG->licenses);
+            $licenses = self::get_licenses();
+            foreach ($licenses as $license) {
+                if (in_array($license->shortname, $activelicenses)) {
+                    // Interpret core license strings for internationalisation.
+                    if (isset($license->custom) && $license->custom == self::CORE_LICENSE) {
+                        $license->fullname = get_string($license->shortname, 'license');
+                    }
+                    $result[$license->shortname] = $license;
+                }
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Get the globally configured active licenses as an array.
+     *
+     * @return array $licenses an associative array of licenses shaped as ['shortname' => 'fullname']
+     */
+    static public function get_active_licenses_as_array() {
+        $activelicenses = self::get_active_licenses();
+
+        $licenses = [];
+        foreach ($activelicenses as $license) {
+            $licenses[$license->shortname] = $license->fullname;
+        }
+
+        return $licenses;
+    }
+
+    /**
+     * Install moodle built-in licenses.
      */
     static public function install_licenses() {
-        $active_licenses = array();
-
-        $license = new stdClass();
-
-        $license->shortname = 'unknown';
-        $license->fullname = 'Unknown license';
-        $license->source = '';
-        $license->enabled = 1;
-        $license->version = '2010033100';
-        $active_licenses[] = $license->shortname;
-        self::add($license);
-
-        $license->shortname = 'allrightsreserved';
-        $license->fullname = 'All rights reserved';
-        $license->source = 'http://en.wikipedia.org/wiki/All_rights_reserved';
-        $license->enabled = 1;
-        $license->version = '2010033100';
-        $active_licenses[] = $license->shortname;
-        self::add($license);
-
-        $license->shortname = 'public';
-        $license->fullname = 'Public Domain';
-        $license->source = 'http://creativecommons.org/licenses/publicdomain/';
-        $license->enabled = 1;
-        $license->version = '2010033100';
-        $active_licenses[] = $license->shortname;
-        self::add($license);
-
-        $license->shortname = 'cc';
-        $license->fullname = 'Creative Commons';
-        $license->source = 'http://creativecommons.org/licenses/by/3.0/';
-        $license->enabled = 1;
-        $license->version = '2010033100';
-        $active_licenses[] = $license->shortname;
-        self::add($license);
-
-        $license->shortname = 'cc-nd';
-        $license->fullname = 'Creative Commons - NoDerivs';
-        $license->source = 'http://creativecommons.org/licenses/by-nd/3.0/';
-        $license->enabled = 1;
-        $license->version = '2010033100';
-        $active_licenses[] = $license->shortname;
-        self::add($license);
-
-        $license->shortname = 'cc-nc-nd';
-        $license->fullname = 'Creative Commons - No Commercial NoDerivs';
-        $license->source = 'http://creativecommons.org/licenses/by-nc-nd/3.0/';
-        $license->enabled = 1;
-        $license->version = '2010033100';
-        $active_licenses[] = $license->shortname;
-        self::add($license);
-
-        $license->shortname = 'cc-nc';
-        $license->fullname = 'Creative Commons - No Commercial';
-        $license->source = 'http://creativecommons.org/licenses/by-nc/3.0/';
-        $license->enabled = 1;
-        $license->version = '2013051500';
-        $active_licenses[] = $license->shortname;
-        self::add($license);
-
-        $license->shortname = 'cc-nc-sa';
-        $license->fullname = 'Creative Commons - No Commercial ShareAlike';
-        $license->source = 'http://creativecommons.org/licenses/by-nc-sa/3.0/';
-        $license->enabled = 1;
-        $license->version = '2010033100';
-        $active_licenses[] = $license->shortname;
-        self::add($license);
-
-        $license->shortname = 'cc-sa';
-        $license->fullname = 'Creative Commons - ShareAlike';
-        $license->source = 'http://creativecommons.org/licenses/by-sa/3.0/';
-        $license->enabled = 1;
-        $license->version = '2010033100';
-        $active_licenses[] = $license->shortname;
-        self::add($license);
-
-        set_config('licenses', implode(',', $active_licenses));
+        global $CFG;
+
+        require_once($CFG->libdir . '/db/upgradelib.php');
+
+        upgrade_core_licenses();
+    }
+
+    /**
+     * Reset the license cache so it rebuilds next time licenses are fetched.
+     */
+    static public function reset_license_cache() {
+        $cache = \cache::make('core', 'license');
+        $cache->delete('licenses');
     }
 }
diff --git a/lib/tests/licenselib_test.php b/lib/tests/licenselib_test.php
new file mode 100644 (file)
index 0000000..ad257fc
--- /dev/null
@@ -0,0 +1,345 @@
+<?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/>.
+
+/**
+ * licenselib tests.
+ *
+ * @package    core
+ * @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();
+
+require_once(__DIR__.'/../licenselib.php');
+
+/**
+ * licenselib tests.
+ *
+ * @package    core
+ * @copyright  2020 Tom Dickman <tom.dickman@catalyst-au.net>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class licenselib_test extends advanced_testcase {
+
+    /**
+     * Test getting licenses from database or cache.
+     */
+    public function test_get_licenses() {
+        $this->resetAfterTest();
+
+        // Reset the cache, to make sure we are not getting cached licenses.
+        $cache = \cache::make('core', 'license');
+        $cache->delete('licenses');
+
+        $licenses = license_manager::get_licenses();
+
+        $this->assertArrayHasKey('unknown', $licenses);
+        $this->assertArrayHasKey('allrightsreserved', $licenses);
+        $this->assertArrayHasKey('public', $licenses);
+        $this->assertArrayHasKey('cc', $licenses);
+        $this->assertArrayHasKey('cc-nd', $licenses);
+        $this->assertArrayHasKey('cc-nc-nd', $licenses);
+        $this->assertArrayHasKey('cc-nc', $licenses);
+        $this->assertArrayHasKey('cc-nc-sa', $licenses);
+        $this->assertArrayHasKey('cc-sa', $licenses);
+
+        // Get the licenses from cache and check again.
+        $licenses = license_manager::get_licenses();
+
+        $this->assertArrayHasKey('unknown', $licenses);
+        $this->assertArrayHasKey('allrightsreserved', $licenses);
+        $this->assertArrayHasKey('public', $licenses);
+        $this->assertArrayHasKey('cc', $licenses);
+        $this->assertArrayHasKey('cc-nd', $licenses);
+        $this->assertArrayHasKey('cc-nc-nd', $licenses);
+        $this->assertArrayHasKey('cc-nc', $licenses);
+        $this->assertArrayHasKey('cc-nc-sa', $licenses);
+        $this->assertArrayHasKey('cc-sa', $licenses);
+    }
+
+    /**
+     * Test saving a license.
+     */
+    public function test_save() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $license = new stdClass();
+        $license->shortname = 'mit';
+        $license->fullname = 'MIT';
+        $license->source = 'https://opensource.org/licenses/MIT';
+        $license->version = '2020020200';
+        $license->custom = license_manager::CUSTOM_LICENSE;
+
+        license_manager::save($license);
+
+        $license = $DB->get_record('license', ['shortname' => 'mit']);
+        $this->assertNotEmpty($license);
+        $this->assertEquals('mit', $license->shortname);
+
+        // Attempting to update a core license should only update sortorder.
+        $license->shortname = 'cc';
+        $license->sortorder = 33;
+        license_manager::save($license);
+
+        $record = $DB->get_record('license', ['id' => $license->id]);
+        $this->assertNotEquals('cc', $record->shortname);
+        $record = $DB->get_record('license', ['shortname' => 'cc']);
+        $this->assertEquals(33, $record->sortorder);
+
+        // Adding a license with existing custom license shortname should update existing license.
+        $updatelicense = new stdClass();
+        $updatelicense->shortname = 'mit';
+        $updatelicense->fullname = 'MIT updated';
+        $updatelicense->source = 'https://en.wikipedia.org/wiki/MIT_License';
+
+        license_manager::save($updatelicense);
+        $actual = $DB->get_record('license', ['shortname' => 'mit']);
+
+        $this->assertEquals($updatelicense->fullname, $actual->fullname);
+        $this->assertEquals($updatelicense->source, $actual->source);
+        // Fields not updated should remain the same.
+        $this->assertEquals($license->version, $actual->version);
+    }
+
+    /**
+     * Test ability to get a license by it's short name.
+     */
+    public function test_get_license_by_shortname() {
+
+        $license = license_manager::get_license_by_shortname('cc-nc');
+        $actual = $license->fullname;
+
+        $this->assertEquals('Creative Commons - No Commercial', $actual);
+        $this->assertNull(license_manager::get_license_by_shortname('somefakelicense'));
+    }
+
+    /**
+     * Test disabling a license.
+     */
+    public function test_disable_license() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        // Manually set license record to enabled for testing.
+        $DB->set_field('license', 'enabled', license_manager::LICENSE_ENABLED, ['shortname' => 'cc-nc']);
+
+        $this->assertTrue(license_manager::disable('cc-nc'));
+
+        $license = license_manager::get_license_by_shortname('cc-nc');
+        $actual = $license->enabled;
+
+        $this->assertEquals(license_manager::LICENSE_DISABLED, $actual);
+    }
+
+    /**
+     * Test enabling a license.
+     */
+    public function test_enable_license() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        // Manually set license record to disabled for testing.
+        $DB->set_field('license', 'enabled', license_manager::LICENSE_DISABLED, ['shortname' => 'cc-nc']);
+
+        $this->assertTrue(license_manager::enable('cc-nc'));
+
+        $license = license_manager::get_license_by_shortname('cc-nc');
+        $actual = $license->enabled;
+
+        $this->assertEquals(license_manager::LICENSE_ENABLED, $actual);
+    }
+
+    /**
+     * Test deleting a custom license.
+     */
+    public function test_delete() {
+        $this->resetAfterTest();
+
+        // Create a custom license.
+        $license = new stdClass();
+        $license->shortname = 'mit';
+        $license->fullname = 'MIT';
+        $license->source = 'https://opensource.org/licenses/MIT';
+        $license->version = '2020020200';
+        $license->custom = license_manager::CUSTOM_LICENSE;
+
+        license_manager::save($license);
+
+        // Should be able to delete a custom license.
+        license_manager::delete($license->shortname);
+        $this->assertNull(license_manager::get_license_by_shortname($license->shortname));
+    }
+
+    /**
+     * Test trying to delete a license currently in use by a file.
+     */
+    public function test_delete_license_in_use_by_file() {
+        $this->resetAfterTest();
+
+        // Create a custom license.
+        $license = new stdClass();
+        $license->shortname = 'mit';
+        $license->fullname = 'MIT';
+        $license->source = 'https://opensource.org/licenses/MIT';
+        $license->version = '2020020200';
+        $license->custom = license_manager::CUSTOM_LICENSE;
+
+        license_manager::save($license);
+
+        // Create a test file with custom license selected.
+        $fs = get_file_storage();
+        $syscontext = context_system::instance();
+        $filerecord = array(
+            'contextid' => $syscontext->id,
+            'component' => 'tool_metadata',
+            'filearea' => 'unittest',
+            'itemid' => 0,
+            'filepath' => '/',
+            'filename' => 'test.doc',
+        );
+        $file = $fs->create_file_from_string($filerecord, 'Test file');
+        $file->set_license($license->shortname);
+
+        // Should not be able to delete a license when in use by a file.
+        $this->expectException(moodle_exception::class);
+        license_manager::delete($license->shortname);
+    }
+
+    /**
+     * Test trying to delete a core license.
+     */
+    public function test_delete_license_core() {
+        // Should not be able to delete a standard/core license.
+        $this->expectException(moodle_exception::class);
+        license_manager::delete('cc-nc');
+    }
+
+    /**
+     * Test trying to delete a license which doesn't exist.
+     */
+    public function test_delete_license_not_exists() {
+        // Should throw an exception if license with shortname doesn't exist.
+        $this->expectException(moodle_exception::class);
+        license_manager::delete('somefakelicense');
+    }
+
+    /**
+     * Test setting active licenses.
+     */
+    public function test_set_active_licenses() {
+        $this->resetAfterTest();
+
+        // Private method used internally, test through disable and enable public methods.
+        license_manager::disable('allrightsreserved');
+        $this->assertStringNotContainsString('allrightsreserved', get_config('', 'licenses'));
+
+        license_manager::enable('allrightsreserved');
+        $this->assertStringContainsString('allrightsreserved', get_config('', 'licenses'));
+    }
+
+    /**
+     * Test getting active licenses.
+     */
+    public function test_get_active_licenses() {
+        $this->resetAfterTest();
+
+        license_manager::disable('allrightsreserved');
+        license_manager::reset_license_cache();
+
+        $licenses = license_manager::get_active_licenses();
+        $this->assertArrayNotHasKey('allrightsreserved', $licenses);
+
+        license_manager::enable('allrightsreserved');
+        license_manager::reset_license_cache();
+
+        $licenses = license_manager::get_active_licenses();
+        $this->assertArrayHasKey('allrightsreserved', $licenses);
+    }
+
+    /**
+     * Test getting active licenses as array.
+     */
+    public function test_get_active_licenses_as_array() {
+        $this->resetAfterTest();
+
+        license_manager::disable('allrightsreserved');
+        license_manager::reset_license_cache();
+
+        $licenses = license_manager::get_active_licenses_as_array();
+        $this->assertIsArray($licenses);
+        $this->assertNotContains('All rights reserved', $licenses);
+
+        license_manager::enable('allrightsreserved');
+        license_manager::reset_license_cache();
+
+        $licenses = license_manager::get_active_licenses_as_array();
+        $this->assertIsArray($licenses);
+        $this->assertContains('All rights reserved', $licenses);
+    }
+
+    /**
+     * Test resetting the license cache.
+     */
+    public function test_reset_license_cache() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $licenses = license_manager::get_licenses();
+
+        $cache = \cache::make('core', 'license');
+        $cachedlicenses = $cache->get('licenses');
+
+        $this->assertNotFalse($cachedlicenses);
+        $this->assertEquals($licenses, $cachedlicenses);
+
+        // Manually delete a license to see if cache persists.
+        $DB->delete_records('license', ['shortname' => 'cc-nc']);
+        $licenses = license_manager::get_licenses();
+
+        $this->assertArrayHasKey('cc-nc', $licenses);
+
+        license_manager::reset_license_cache();
+
+        $licenses = license_manager::get_licenses();
+        $this->assertArrayNotHasKey('cc-nc', $licenses);
+    }
+
+    /**
+     * Test that all licenses are installed correctly.
+     */
+    public function test_install_licenses() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $DB->delete_records('license');
+
+        license_manager::install_licenses();
+
+        $expectedshortnames = ['allrightsreserved', 'cc', 'cc-nc', 'cc-nc-nd', 'cc-nc-sa', 'cc-nd', 'cc-sa', 'public', 'unknown'];
+        $actualshortnames = $DB->get_records_menu('license', null, '', 'id, shortname');
+
+        foreach ($expectedshortnames as $expectedshortname) {
+            $this->assertContains($expectedshortname, $actualshortnames);
+        }
+    }
+}
index b4cd3fd..a0e3b48 100644 (file)
@@ -1051,4 +1051,33 @@ class core_upgradelib_testcase extends advanced_testcase {
 
         $this->assertEquals(0, $DB->count_records_select('analytics_models', $select, $params));
     }
+
+    /**
+     * Test the functionality of {@link upgrade_core_licenses} function.
+     */
+    public function test_upgrade_core_licenses() {
+        global $CFG, $DB;
+
+        $this->resetAfterTest();
+
+        // Emulate that upgrade is in process.
+        $CFG->upgraderunning = time();
+
+        $deletedcorelicenseshortname = 'unknown';
+        $DB->delete_records('license', ['shortname' => $deletedcorelicenseshortname]);
+
+        upgrade_core_licenses();
+
+        $expectedshortnames = ['allrightsreserved', 'cc', 'cc-nc', 'cc-nc-nd', 'cc-nc-sa', 'cc-nd', 'cc-sa', 'public'];
+        $licenses = $DB->get_records('license');
+
+        foreach ($licenses as $license) {
+            $this->assertContains($license->shortname, $expectedshortnames);
+            $this->assertObjectHasAttribute('custom', $license);
+            $this->assertObjectHasAttribute('sortorder', $license);
+        }
+        // A core license which was deleted prior to upgrade should not be reinstalled.
+        $actualshortnames = $DB->get_records_menu('license', null, '', 'id, shortname');
+        $this->assertNotContains($deletedcorelicenseshortname, $actualshortnames);
+    }
 }
index 240147f..2f8744a 100644 (file)
@@ -1201,7 +1201,7 @@ M.core_filepicker.init = function(Y, options) {
 
             // TODO MDL-32532: attributes 'hasauthor' and 'haslicense' need to be obsolete,
             selectnode.one('.fp-setauthor input').set('value', args.author ? args.author : this.options.author);
-            this.set_selected_license(selectnode.one('.fp-setlicense'), args.license);
+            this.populateLicensesSelect(selectnode.one('.fp-setlicense select'), args);
             selectnode.one('form #filesource-'+client_id).set('value', args.source);
             selectnode.one('form #filesourcekey-'+client_id).set('value', args.sourcekey);
 
@@ -1239,7 +1239,6 @@ M.core_filepicker.init = function(Y, options) {
             selectnode.all('.fp-linktype-2,.fp-linktype-1,.fp-linktype-4,.fp-linktype-8').each(function (node) {
                 node.one('input').on('change', changelinktype, this);
             });
-            this.populate_licenses_select(selectnode.one('.fp-setlicense select'));
             // register event on clicking submit button
             getfile.on('click', function(e) {
                 e.preventDefault();
@@ -1257,7 +1256,9 @@ M.core_filepicker.init = function(Y, options) {
                     if (origlicense) {
                         origlicense = origlicense.getContent();
                     }
-                    this.set_preference('recentlicense', license.get('value'));
+                    if (this.options.rememberuserlicensepref) {
+                        this.set_preference('recentlicense', license.get('value'));
+                    }
                 }
                 params['author'] = selectnode.one('.fp-setauthor input').get('value');
 
@@ -1767,37 +1768,29 @@ M.core_filepicker.init = function(Y, options) {
                 callback: this.display_response
             }, true);
         },
-        populate_licenses_select: function(node) {
-            if (!node) {
+        populateLicensesSelect: function(licensenode, filenode) {
+            if (!licensenode) {
                 return;
             }
-            node.setContent('');
-            var licenses = this.options.licenses;
-            var recentlicense = this.get_preference('recentlicense');
-            if (recentlicense) {
-                this.options.defaultlicense=recentlicense;
+            licensenode.setContent('');
+            var selectedlicense = this.options.defaultlicense;
+            if (filenode) {
+                // File has a license already, use it.
+                selectedlicense = filenode.license;
+            } else if (this.options.rememberuserlicensepref) {
+                selectedlicense = this.get_preference('recentlicense');
             }
+            var licenses = this.options.licenses;
             for (var i in licenses) {
-                var option = Y.Node.create('<option/>').
-                    set('selected', (this.options.defaultlicense==licenses[i].shortname)).
+                // Include the file's current license, even if not enabled, to prevent displaying
+                // misleading information about which license the file currently has assigned to it.
+                if (licenses[i].enabled == true || (filenode !== undefined && licenses[i].shortname === filenode.license)) {
+                    var option = Y.Node.create('<option/>').
+                    set('selected', (licenses[i].shortname == selectedlicense)).
                     set('value', licenses[i].shortname).
                     setContent(Y.Escape.html(licenses[i].fullname));
-                node.appendChild(option)
-            }
-        },
-        set_selected_license: function(node, value) {
-            var licenseset = false;
-            node.all('option').each(function(el) {
-                if (el.get('value')==value || el.getContent()==value) {
-                    el.set('selected', true);
-                    licenseset = true;
+                    licensenode.appendChild(option);
                 }
-            });
-            if (!licenseset) {
-                // we did not find the value in the list
-                var recentlicense = this.get_preference('recentlicense');
-                node.all('option[selected]').set('selected', false);
-                node.all('option[value='+recentlicense+']').set('selected', true);
             }
         },
         create_object_container: function(data) {
@@ -1828,7 +1821,7 @@ M.core_filepicker.init = function(Y, options) {
             content.one('.fp-saveas input').set('name', 'title');
             content.one('.fp-setauthor input').setAttrs({name:'author', value:this.options.author});
             content.one('.fp-setlicense select').set('name', 'license');
-            this.populate_licenses_select(content.one('.fp-setlicense select'))
+            this.populateLicensesSelect(content.one('.fp-setlicense select'));
             // append hidden inputs to the upload form
             content.one('form').appendChild(Y.Node.create('<input/>').
                 setAttrs({type:'hidden',name:'itemid',value:this.options.itemid}));
@@ -1843,7 +1836,9 @@ M.core_filepicker.init = function(Y, options) {
                 e.preventDefault();
                 var license = content.one('.fp-setlicense select');
 
-                this.set_preference('recentlicense', license.get('value'));
+                if (this.options.rememberuserlicensepref) {
+                    this.set_preference('recentlicense', license.get('value'));
+                }
                 if (!content.one('.fp-file input').get('value')) {
                     scope.print_msg(M.util.get_string('nofilesattached', 'repository'), 'error');
                     return false;
index b5296d6..a840ff5 100644 (file)
@@ -3115,21 +3115,14 @@ final class repository_type_form extends moodleform {
  *          accepted_types
  */
 function initialise_filepicker($args) {
-    global $CFG, $USER, $PAGE, $OUTPUT;
+    global $CFG, $USER, $PAGE;
     static $templatesinitialized = array();
     require_once($CFG->libdir . '/licenselib.php');
 
     $return = new stdClass();
-    $licenses = array();
-    if (!empty($CFG->licenses)) {
-        $array = explode(',', $CFG->licenses);
-        foreach ($array as $license) {
-            $l = new stdClass();
-            $l->shortname = $license;
-            $l->fullname = get_string($license, 'license');
-            $licenses[] = $l;
-        }
-    }
+
+    $licenses = license_manager::get_licenses();
+
     if (!empty($CFG->sitedefaultlicense)) {
         $return->defaultlicense = $CFG->sitedefaultlicense;
     }
@@ -3173,6 +3166,8 @@ function initialise_filepicker($args) {
         $return->externallink = true;
     }
 
+    $return->rememberuserlicensepref = (bool) get_config(null, 'rememberuserlicensepref');
+
     $return->userprefs = array();
     $return->userprefs['recentrepository'] = get_user_preferences('filepicker_recentrepository', '');
     $return->userprefs['recentlicense'] = get_user_preferences('filepicker_recentlicense', '');
index 5962057..0b3c297 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2020052200.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2020052200.01;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.
 $release  = '3.9dev+ (Build: 20200522)'; // Human-friendly version name