diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6817050 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.remote-sync.json diff --git a/README.md b/README.md index 07b251f..9b4bfad 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ moodle-availability_role ======================== -[![Moodle Plugin CI](https://github.com/moodle-an-hochschulen/moodle-availability_role/workflows/Moodle%20Plugin%20CI/badge.svg?branch=master)](https://github.com/moodle-an-hochschulen/moodle-availability_role/actions?query=workflow%3A%22Moodle+Plugin+CI%22+branch%3Amaster) +[![Moodle Plugin CI](https://github.com/moodle-an-hochschulen/moodle-availability_role/workflows/Moodle%20Plugin%20CI/badge.svg?branch=MOODLE_402_STABLE)](https://github.com/moodle-an-hochschulen/moodle-availability_role/actions?query=workflow%3A%22Moodle+Plugin+CI%22+branch%3AMOODLE_402_STABLE) -Moodle availability plugin which lets users restrict resources, activities and sections based on roles +Moodle availability plugin which lets users restrict resources, activities and sections based on roles. Requirements @@ -15,7 +15,7 @@ This plugin requires Moodle 4.3+ Motivation for this plugin -------------------------- -If your teachers want to restrict activities / resources / sections in their course to a subset of the course participants and these course participants share a common course role, this plugin is for you. +If your teachers want to restrict activities / resources / sections in their course to a subset of the course participants and these course participants share a common course role, a course category role or global role, this plugin is for you. Have a look at an example: diff --git a/classes/condition.php b/classes/condition.php index 4ae6cd5..b37ddc0 100644 --- a/classes/condition.php +++ b/classes/condition.php @@ -36,6 +36,11 @@ class condition extends \core_availability\condition { /** @var int ID of role that this condition requires */ protected $roleid = 0; + /** @var int ID of type that this condition requires */ + protected $typeid = 0; + public const ROLETYPE_COURSE = 0; + public const ROLETYPE_COURSECAT = 1; + public const ROLETYPE_CORE = 2; /** * Constructor. @@ -50,6 +55,11 @@ public function __construct($structure) { } else { throw new \coding_exception('Invalid ->id for role condition'); } + if (!empty($structure->typeid)) { + $this->typeid = $structure->typeid; + } else { + $this->typeid = self::ROLETYPE_COURSE; + } } /** @@ -61,6 +71,7 @@ public function save() { $result = (object)['type' => 'role']; if ($this->roleid) { $result->id = $this->roleid; + $result->typeid = $this->typeid; } else { $result->activity = true; } @@ -115,40 +126,63 @@ public function update_after_restore($restoreid, $courseid, \base_logger $logger */ public function is_available($not, \core_availability\info $info, $grabthelot, $userid) { global $USER, $CFG; - $context = \context_course::instance($info->get_course()->id); $allow = false; - // Is the user's course role switched? - if (!empty($USER->access['rsw'][$context->path])) { - // Check only switched role. - if ($USER->access['rsw'][$context->path] == $this->roleid) { - $allow = true; - } - // Or is the user currently having his own role(s)? - } else { - // Check all of the user's course roles. - foreach (get_user_roles($context, $userid) as $role) { - if ($role->roleid == $this->roleid) { - $allow = true; - break; + switch($this->typeid) { + case self::ROLETYPE_CORE: + $context = \context_system::instance(); + foreach (get_user_roles($context, $userid) as $role) { + if ($role->roleid == $this->roleid) { + $allow = true; + break; + } } - } + break; + case self::ROLETYPE_COURSECAT: + $context = \context_coursecat::instance($info->get_course()->category); + // Check all of the user's course roles. + foreach (get_user_roles($context, $userid) as $role) { + if ($role->roleid == $this->roleid) { + $allow = true; + break; + } + } + break; + default: + $context = \context_course::instance($info->get_course()->id); + // Is the user's course role switched? + if (!empty($USER->access['rsw'][$context->path])) { + // Check only switched role. + if ($USER->access['rsw'][$context->path] == $this->roleid) { + $allow = true; + } + // Or is the user currently having his own role(s)? + } else { + // Check all of the user's course roles. + foreach (get_user_roles($context, $userid) as $role) { + if ($role->roleid == $this->roleid) { + $allow = true; + break; + } + } + } + } - // As get_user_roles only returns roles for enrolled users, we have to check whether a user - // is viewing the course as guest or is not logged in separately. + // As get_user_roles only returns roles for enrolled users, we have to check whether a user + // is viewing the course as guest or is not logged in separately. - // Is the user not logged in? - if (empty($userid) || isguestuser($userid)) { - if ($CFG->notloggedinroleid == $this->roleid) { - $allow = true; - } + // Is the user not logged in? + if (empty($userid) || isguestuser($userid)) { + if ($CFG->notloggedinroleid == $this->roleid) { + $allow = true; } + } - // Is the user viewing the course as guest? - if (is_guest($context, $userid)) { - if (get_guest_role()->id == $this->roleid) { - $allow = true; - } + // Is the user viewing the course as guest? + $context = \context_course::instance($info->get_course()->id); + if (is_guest($context, $userid)) { + if (get_guest_role()->id == $this->roleid) { + $allow = true; } } diff --git a/classes/frontend.php b/classes/frontend.php index f72a438..eb8c8bd 100644 --- a/classes/frontend.php +++ b/classes/frontend.php @@ -45,21 +45,56 @@ class frontend extends \core_availability\frontend { * @return array */ protected function get_javascript_init_params($course, \cm_info $cm = null, \section_info $section = null) { + global $CFG, $DB; + require_once($CFG->dirroot . '/availability/condition/role/classes/condition.php'); + // Change to JS array format and return. - $jsarray = []; - $context = \context_course::instance($course->id); + $idcourse = get_string('course'); + $idcoursecat = get_string('coursecategory'); + $idglobal = get_string('coresystem'); + $jsarray = array(); // Get all roles for course. + $context = \context_course::instance($course->id); $roles = $this->get_course_roles($context); + foreach ($roles as $rec) { + $jsarray[] = (object)array( + 'id' => $rec->id, + 'name' => (!empty($rec->name) ? $rec->name : $rec->localname), + 'type' => $idcourse, + 'typeid' => \availability_role\condition::ROLETYPE_COURSE, + ); + } + + $roles = explode(',', get_config('availability_role', 'coursecatroles')); + foreach ($roles as $rec) { + $rec = $DB->get_record('role', array('id' => $rec)); + if (empty($rec->id)) { + continue; + } + $jsarray[] = (object)array( + 'id' => $rec->id, + 'name' => (!empty($rec->name) ? $rec->name : $rec->shortname), + 'type' => $idcoursecat, + 'typeid' => \availability_role\condition::ROLETYPE_COURSECAT, + ); + } + $roles = explode(',', get_config('availability_role', 'globalroles')); foreach ($roles as $rec) { - $jsarray[] = (object)[ + $rec = $DB->get_record('role', array('id' => $rec)); + if (empty($rec->id)) { + continue; + } + $jsarray[] = (object)array( 'id' => $rec->id, - 'name' => $rec->localname, - ]; + 'name' => (!empty($rec->name) ? $rec->name : $rec->shortname), + 'type' => $idglobal, + 'typeid' => \availability_role\condition::ROLETYPE_CORE, + ); } - return [$jsarray]; + return array($jsarray); } /** diff --git a/lang/en/availability_role.php b/lang/en/availability_role.php index d017aa2..557c62a 100644 --- a/lang/en/availability_role.php +++ b/lang/en/availability_role.php @@ -33,6 +33,10 @@ $string['privacy:metadata'] = 'The Restriction by course role plugin does not store any personal data.'; $string['requires_role'] = 'You are a(n) {$a}'; $string['requires_notrole'] = 'You are not a(n) {$a}'; +$string['setting_coursecatroles'] = 'Supported roles'; +$string['setting_coursecatroles:description'] = 'Specify which courecategory roles are selectable by users to restrict access.'; +$string['setting_globalroles'] = 'Supported roles'; +$string['setting_globalroles:description'] = 'Specify which global roles are selectable by users to restrict access.'; $string['setting_supportedrolesheading'] = 'Supported roles'; $string['setting_supportguestrole'] = 'Guest role'; $string['setting_supportguestrole_desc'] = 'If activated, the availability of activities can be restricted to or forbidden for users that are viewing a course as guest.'; diff --git a/settings.php b/settings.php index c57aae3..6ff01be 100644 --- a/settings.php +++ b/settings.php @@ -45,5 +45,48 @@ $description = get_string('setting_supportnotloggedinrole_desc', 'availability_role', null, true); $setting = new admin_setting_configcheckbox($name, $title, $description, 0); $settings->add($setting); -} + // Course category roles. + $sql = "SELECT r.* + FROM {role} AS r, {role_context_levels} AS rcl + WHERE r.id=rcl.roleid + AND rcl.contextlevel = ? + ORDER BY r.name ASC"; + $roles = $DB->get_records_sql($sql, array(CONTEXT_COURSECAT)); + $options = array(); + foreach ($roles as $role) { + $options[$role->id] = (!empty($role->name) ? $role->name : $role->shortname); + } + + $settings->add( + new admin_setting_configmultiselect( + 'availability_role/coursecatroles', + get_string('setting_coursecatroles', 'availability_role'), + get_string('setting_coursecatroles:description', 'availability_role'), + get_config('availability_role', 'coursecatroles'), + $options + ) + ); + + // Global roles. + $sql = "SELECT r.* + FROM {role} AS r, {role_context_levels} AS rcl + WHERE r.id=rcl.roleid + AND rcl.contextlevel = ? + ORDER BY r.name ASC"; + $roles = $DB->get_records_sql($sql, array(CONTEXT_SYSTEM)); + $options = array(); + foreach ($roles as $role) { + $options[$role->id] = (!empty($role->name) ? $role->name : $role->shortname); + } + + $settings->add( + new admin_setting_configmultiselect( + 'availability_role/globalroles', + get_string('setting_globalroles', 'availability_role'), + get_string('setting_globalroles:description', 'availability_role'), + get_config('availability_role', 'globalroles'), + $options + ) + ); +} diff --git a/version.php b/version.php index d7317ed..cc3268b 100644 --- a/version.php +++ b/version.php @@ -20,14 +20,16 @@ * @package availability_role * @copyright 2015 Bence Laky, Synergy Learning UK * on behalf of Alexander Bias, Ulm University + * extended to coursecat- and global roles + * by Robert Schrenk, Center for Learningmanagement * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'availability_role'; -$plugin->version = 2023102000; -$plugin->release = 'v4.3-r1'; -$plugin->requires = 2023100900; -$plugin->supported = [403, 403]; +$plugin->version = 2023090100; +$plugin->release = 'v4.2-r1'; +$plugin->requires = 2023042400; +$plugin->supported = [402, 402]; $plugin->maturity = MATURITY_STABLE; diff --git a/yui/build/moodle-availability_role-form/moodle-availability_role-form-debug.js b/yui/build/moodle-availability_role-form/moodle-availability_role-form-debug.js index 15de974..61f7716 100644 --- a/yui/build/moodle-availability_role-form/moodle-availability_role-form-debug.js +++ b/yui/build/moodle-availability_role-form/moodle-availability_role-form-debug.js @@ -29,16 +29,29 @@ M.availability_role.form.getNode = function(json) { '' + ''; var node = Y.Node.create('' + html + ''); // Set initial value if specified. if (json.id !== undefined && - node.one('select[name=id] > option[value=' + json.id + ']')) { - node.one('select[name=id]').set('value', '' + json.id); + node.one('select[name=id] option[value=' + json.typeid + '_' + json.id + ']')) { + node.one('select[name=id]').set('value', json.typeid + '_' + json.id); } // Add event handlers (first time only). @@ -59,7 +72,9 @@ M.availability_role.form.fillValue = function(value, node) { if (selected === 'choose') { value.id = 'choose'; } else { - value.id = parseInt(selected, 10); + selected = selected.split('_'); + value.typeid = parseInt(selected[0], 10); + value.id = parseInt(selected[1], 10); } }; diff --git a/yui/build/moodle-availability_role-form/moodle-availability_role-form-min.js b/yui/build/moodle-availability_role-form/moodle-availability_role-form-min.js index c18e1fa..fae5599 100644 --- a/yui/build/moodle-availability_role-form/moodle-availability_role-form-min.js +++ b/yui/build/moodle-availability_role-form/moodle-availability_role-form-min.js @@ -1 +1 @@ -YUI.add("moodle-availability_role-form",function(a,e){M.availability_role=M.availability_role||{},M.availability_role.form=a.Object(M.core_availability.plugin),M.availability_role.form.roles=null,M.availability_role.form.initInner=function(e){this.roles=e},M.availability_role.form.getNode=function(e){var i,l='",i=a.Node.create(""+l+""),e.id!==undefined&&i.one("select[name=id] > option[value="+e.id+"]")&&i.one("select[name=id]").set("value",""+e.id),M.availability_role.form.addedEvents||(M.availability_role.form.addedEvents=!0,a.one(".availability-field").delegate("change",function(){M.core_availability.form.update()},".availability_role select")),i},M.availability_role.form.fillValue=function(e,i){i=i.one("select[name=id]").get("value");e.id="choose"===i?"choose":parseInt(i,10)},M.availability_role.form.fillErrors=function(e,i){var l={};this.fillValue(l,i),"choose"===l.id&&e.push("availability_role:error_selectrole")}},"@VERSION@",{requires:["base","node","event","moodle-core_availability-form"]}); \ No newline at end of file +YUI.add("moodle-availability_role-form",function(b,a){M.availability_role=M.availability_role||{};M.availability_role.form=b.Object(M.core_availability.plugin);M.availability_role.form.roles=null;M.availability_role.form.initInner=function(c){this.roles=c};M.availability_role.form.getNode=function(g){var f='";var h=b.Node.create(""+f+"");if(g.id!==undefined&&h.one("select[name=id] option[value="+g.typeid+"_"+g.id+"]")){h.one("select[name=id]").set("value",g.typeid+"_"+g.id)}if(!M.availability_role.form.addedEvents){M.availability_role.form.addedEvents=true;var d=b.one(".availability-field");d.delegate("change",function(){M.core_availability.form.update()},".availability_role select")}return h};M.availability_role.form.fillValue=function(e,d){var c=d.one("select[name=id]").get("value");if(c==="choose"){e.id="choose"}else{c=c.split("_");e.typeid=parseInt(c[0],10);e.id=parseInt(c[1],10)}};M.availability_role.form.fillErrors=function(e,c){var d={};this.fillValue(d,c);if(d.id==="choose"){e.push("availability_role:error_selectrole")}}},"@VERSION@",{requires:["base","node","event","moodle-core_availability-form"]}); \ No newline at end of file diff --git a/yui/build/moodle-availability_role-form/moodle-availability_role-form.js b/yui/build/moodle-availability_role-form/moodle-availability_role-form.js index 15de974..61f7716 100644 --- a/yui/build/moodle-availability_role-form/moodle-availability_role-form.js +++ b/yui/build/moodle-availability_role-form/moodle-availability_role-form.js @@ -29,16 +29,29 @@ M.availability_role.form.getNode = function(json) { '' + ''; var node = Y.Node.create('' + html + ''); // Set initial value if specified. if (json.id !== undefined && - node.one('select[name=id] > option[value=' + json.id + ']')) { - node.one('select[name=id]').set('value', '' + json.id); + node.one('select[name=id] option[value=' + json.typeid + '_' + json.id + ']')) { + node.one('select[name=id]').set('value', json.typeid + '_' + json.id); } // Add event handlers (first time only). @@ -59,7 +72,9 @@ M.availability_role.form.fillValue = function(value, node) { if (selected === 'choose') { value.id = 'choose'; } else { - value.id = parseInt(selected, 10); + selected = selected.split('_'); + value.typeid = parseInt(selected[0], 10); + value.id = parseInt(selected[1], 10); } }; diff --git a/yui/src/form/js/form.js b/yui/src/form/js/form.js index 88f1c13..2b4fa0d 100644 --- a/yui/src/form/js/form.js +++ b/yui/src/form/js/form.js @@ -27,16 +27,29 @@ M.availability_role.form.getNode = function(json) { '' + ''; var node = Y.Node.create('' + html + ''); // Set initial value if specified. if (json.id !== undefined && - node.one('select[name=id] > option[value=' + json.id + ']')) { - node.one('select[name=id]').set('value', '' + json.id); + node.one('select[name=id] option[value=' + json.typeid + '_' + json.id + ']')) { + node.one('select[name=id]').set('value', json.typeid + '_' + json.id); } // Add event handlers (first time only). @@ -57,7 +70,9 @@ M.availability_role.form.fillValue = function(value, node) { if (selected === 'choose') { value.id = 'choose'; } else { - value.id = parseInt(selected, 10); + selected = selected.split('_'); + value.typeid = parseInt(selected[0], 10); + value.id = parseInt(selected[1], 10); } };