diff --git a/calendar/amd/build/event_form.min.js b/calendar/amd/build/event_form.min.js index 0abfe871b35..0d3bc5dbfb0 100644 --- a/calendar/amd/build/event_form.min.js +++ b/calendar/amd/build/event_form.min.js @@ -1 +1 @@ -define(["jquery"],function(a){var b={EVENT_GROUP_COURSE_ID:'[name="groupcourseid"]',EVENT_GROUP_ID:'[name="groupid"]',SELECT_OPTION:"option"},c=function(c){c.find(b.EVENT_GROUP_ID).find(b.SELECT_OPTION).each(function(b,c){c=a(c);var d=c.attr("value"),e=d.split("-"),f=e[0];c.attr("data-course-id",f)})},d=function(c){var d=c.find(b.EVENT_GROUP_COURSE_ID),e=c.find(b.EVENT_GROUP_ID),f=e.find(b.SELECT_OPTION),g=function(){var b=d.val(),c=null,g=!1;f.each(function(d,e){e=a(e),e.attr("data-course-id")==b?(e.removeClass("hidden"),e.prop("disabled",!1),g=!0,(null===c||e.attr("selected"))&&(c=d)):(e.addClass("hidden"),e.prop("disabled",!0))}),g?e.prop("disabled",!1):e.prop("disabled",!0),e.prop("selectedIndex",c)};d.on("change",g),g()},e=function(b){var e=a("#"+b);c(e),d(e)};return{init:e}}); \ No newline at end of file +define(["jquery","core_calendar/repository"],function(a,b){var c={EVENT_GROUP_COURSE_ID:'[name="groupcourseid"]',EVENT_GROUP_ID:'[name="groupid"]',SELECT_OPTION:"option"},d=function(d){var e=d.find(c.EVENT_GROUP_COURSE_ID),f=function(b){var e=d.find(c.EVENT_GROUP_ID),f=e.find(c.SELECT_OPTION),g=a(b);f.remove(),e.prop("disabled",!1),g.each(function(b,c){a(e).append(a("").attr("value",c.id).text(c.name))})};e.on("change",function(){var a=d.find(c.EVENT_GROUP_COURSE_ID).val();b.getCourseGroupsData(a).then(function(a){return f(a)})["catch"](Notification.exception)})},e=function(b){var c=a("#"+b);d(c)};return{init:e}}); \ No newline at end of file diff --git a/calendar/amd/build/repository.min.js b/calendar/amd/build/repository.min.js index 6b1db42b10c..894dcf99106 100644 --- a/calendar/amd/build/repository.min.js +++ b/calendar/amd/build/repository.min.js @@ -1 +1 @@ -define(["jquery","core/ajax"],function(a,b){var c=function(a,c){"undefined"==typeof c&&(c=!1);var d={methodname:"core_calendar_delete_calendar_events",args:{events:[{eventid:a,repeat:c}]}};return b.call([d])[0]},d=function(a){var c={methodname:"core_calendar_get_calendar_event_by_id",args:{eventid:a}};return b.call([c])[0]},e=function(a){var c={methodname:"core_calendar_submit_create_update_form",args:{formdata:a}};return b.call([c])[0]},f=function(a,c,d,e,f,g){var h={methodname:"core_calendar_get_calendar_monthly_view",args:{year:a,month:c,courseid:d,categoryid:e,includenavigation:f,mini:g}};return b.call([h])[0]},g=function(a,c,d,e,f){var g={methodname:"core_calendar_get_calendar_day_view",args:{year:a,month:c,day:d,courseid:e,categoryid:f}};return b.call([g])[0]},h=function(a,c){var d={methodname:"core_calendar_update_event_start_day",args:{eventid:a,daytimestamp:c}};return b.call([d])[0]},i=function(a,c){var d={methodname:"core_calendar_get_calendar_upcoming_view",args:{courseid:a,categoryid:c}};return b.call([d])[0]};return{getEventById:d,deleteEvent:c,updateEventStartDay:h,submitCreateUpdateForm:e,getCalendarMonthData:f,getCalendarDayData:g,getCalendarUpcomingData:i}}); \ No newline at end of file +define(["jquery","core/ajax"],function(a,b){var c=function(a,c){"undefined"==typeof c&&(c=!1);var d={methodname:"core_calendar_delete_calendar_events",args:{events:[{eventid:a,repeat:c}]}};return b.call([d])[0]},d=function(a){var c={methodname:"core_calendar_get_calendar_event_by_id",args:{eventid:a}};return b.call([c])[0]},e=function(a){var c={methodname:"core_calendar_submit_create_update_form",args:{formdata:a}};return b.call([c])[0]},f=function(a,c,d,e,f,g){var h={methodname:"core_calendar_get_calendar_monthly_view",args:{year:a,month:c,courseid:d,categoryid:e,includenavigation:f,mini:g}};return b.call([h])[0]},g=function(a,c,d,e,f){var g={methodname:"core_calendar_get_calendar_day_view",args:{year:a,month:c,day:d,courseid:e,categoryid:f}};return b.call([g])[0]},h=function(a,c){var d={methodname:"core_calendar_update_event_start_day",args:{eventid:a,daytimestamp:c}};return b.call([d])[0]},i=function(a,c){var d={methodname:"core_calendar_get_calendar_upcoming_view",args:{courseid:a,categoryid:c}};return b.call([d])[0]},j=function(a){var c={methodname:"core_group_get_course_groups",args:{courseid:a}};return b.call([c])[0]};return{getEventById:d,deleteEvent:c,updateEventStartDay:h,submitCreateUpdateForm:e,getCalendarMonthData:f,getCalendarDayData:g,getCalendarUpcomingData:i,getCourseGroupsData:j}}); \ No newline at end of file diff --git a/calendar/amd/src/event_form.js b/calendar/amd/src/event_form.js index 2b6707d80f9..b64639aa6ac 100644 --- a/calendar/amd/src/event_form.js +++ b/calendar/amd/src/event_form.js @@ -21,42 +21,12 @@ * @copyright 2017 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['jquery'], function($) { +define(['jquery', 'core_calendar/repository'], function($, CalendarRepository) { var SELECTORS = { EVENT_GROUP_COURSE_ID: '[name="groupcourseid"]', EVENT_GROUP_ID: '[name="groupid"]', - SELECT_OPTION: 'option', - }; - - /** - * Parse the group id select element in the event form and pull out - * the course id from the value to allow us to toggle other select - * elements based on the course id for the group a user selects. - * - * This is a little hacky but I couldn't find a better way to pass - * the course id for each group id with the limitations of mforms. - * - * The group id options are rendered with a value like: - * "-" - * E.g. - * For a group with id 10 in a course with id 3 the value of the - * option will be 3-10. - * - * @method parseGroupSelect - * @param {object} formElement The root form element - */ - var parseGroupSelect = function(formElement) { - formElement.find(SELECTORS.EVENT_GROUP_ID) - .find(SELECTORS.SELECT_OPTION) - .each(function(index, element) { - element = $(element); - var value = element.attr('value'); - var splits = value.split('-'); - var courseId = splits[0]; - - element.attr('data-course-id', courseId); - }); + SELECT_OPTION: 'option' }; /** @@ -69,39 +39,29 @@ define(['jquery'], function($) { */ var addCourseGroupSelectListeners = function(formElement) { var courseGroupSelect = formElement.find(SELECTORS.EVENT_GROUP_COURSE_ID); - var groupSelect = formElement.find(SELECTORS.EVENT_GROUP_ID); - var groupSelectOptions = groupSelect.find(SELECTORS.SELECT_OPTION); - var filterGroupSelectOptions = function() { - var selectedCourseId = courseGroupSelect.val(); - var selectedIndex = null; - var hasGroups = false; - groupSelectOptions.each(function(index, element) { - element = $(element); - if (element.attr('data-course-id') == selectedCourseId) { - element.removeClass('hidden'); - element.prop('disabled', false); - hasGroups = true; - if (selectedIndex === null || element.attr('selected')) { - selectedIndex = index; - } - } else { - element.addClass('hidden'); - element.prop('disabled', true); - } + var loadGroupSelectOptions = function(groups) { + var groupSelect = formElement.find(SELECTORS.EVENT_GROUP_ID), + groupSelectOptions = groupSelect.find(SELECTORS.SELECT_OPTION), + courseGroups = $(groups); + + // Let's clear all options first. + groupSelectOptions.remove(); + groupSelect.prop("disabled", false); + courseGroups.each(function(id, group) { + $(groupSelect).append($("").attr("value", group.id).text(group.name)); }); - - if (hasGroups) { - groupSelect.prop('disabled', false); - } else { - groupSelect.prop('disabled', true); - } - - groupSelect.prop('selectedIndex', selectedIndex); }; - courseGroupSelect.on('change', filterGroupSelectOptions); - filterGroupSelectOptions(); + // If the user choose a course in the selector do a WS request to get groups. + courseGroupSelect.on('change', function() { + var courseId = formElement.find(SELECTORS.EVENT_GROUP_COURSE_ID).val(); + CalendarRepository.getCourseGroupsData(courseId) + .then(function(groups) { + return loadGroupSelectOptions(groups); + }) + .catch(Notification.exception); + }); }; /** @@ -112,8 +72,6 @@ define(['jquery'], function($) { */ var init = function(formId) { var formElement = $('#' + formId); - - parseGroupSelect(formElement); addCourseGroupSelectListeners(formElement); }; diff --git a/calendar/amd/src/repository.js b/calendar/amd/src/repository.js index 049f8191852..d8166fae7e4 100644 --- a/calendar/amd/src/repository.js +++ b/calendar/amd/src/repository.js @@ -181,6 +181,23 @@ define(['jquery', 'core/ajax'], function($, Ajax) { return Ajax.call([request])[0]; }; + /** + * Get the groups by course id. + * + * @param {Number} courseid The course id to fetch the groups from. + * @return {promise} Resolved with the course groups. + */ + var getCourseGroupsData = function(courseid) { + var request = { + methodname: 'core_group_get_course_groups', + args: { + courseid: courseid + } + }; + + return Ajax.call([request])[0]; + }; + return { getEventById: getEventById, deleteEvent: deleteEvent, @@ -188,6 +205,7 @@ define(['jquery', 'core/ajax'], function($, Ajax) { submitCreateUpdateForm: submitCreateUpdateForm, getCalendarMonthData: getCalendarMonthData, getCalendarDayData: getCalendarDayData, - getCalendarUpcomingData: getCalendarUpcomingData + getCalendarUpcomingData: getCalendarUpcomingData, + getCourseGroupsData: getCourseGroupsData }; }); diff --git a/calendar/classes/local/event/forms/create.php b/calendar/classes/local/event/forms/create.php index 50e6d8158b7..ab014810824 100644 --- a/calendar/classes/local/event/forms/create.php +++ b/calendar/classes/local/event/forms/create.php @@ -66,9 +66,11 @@ class create extends \moodleform { $mform = $this->_form; $starttime = isset($this->_customdata['starttime']) ? $this->_customdata['starttime'] : 0; $editoroptions = !(empty($this->_customdata['editoroptions'])) ? $this->_customdata['editoroptions'] : null; - $eventtypes = calendar_get_all_allowed_types(); + $courseid = !(empty($this->_customdata['courseid'])) ? $this->_customdata['courseid'] : null; - if (empty($eventtypes)) { + $eventtypes = calendar_get_allowed_event_types($courseid); + + if (in_array(true, $eventtypes, true) === false) { print_error('nopermissiontoupdatecalendar'); } @@ -116,18 +118,20 @@ class create extends \moodleform { * @return array */ public function validation($data, $files) { - global $DB, $CFG; + global $DB; $errors = parent::validation($data, $files); - $eventtypes = calendar_get_all_allowed_types(); $eventtype = isset($data['eventtype']) ? $data['eventtype'] : null; $coursekey = ($eventtype == 'group') ? 'groupcourseid' : 'courseid'; - if (empty($eventtype) || !isset($eventtypes[$eventtype])) { + $courseid = (!empty($data[$coursekey])) ? $data[$coursekey] : null; + $eventtypes = calendar_get_allowed_event_types($courseid); + + if (empty($eventtype) || !isset($eventtypes[$eventtype]) || $eventtypes[$eventtype] == false) { $errors['eventtype'] = get_string('invalideventtype', 'calendar'); } - if (isset($data[$coursekey]) && $data[$coursekey] > 0) { - if ($course = $DB->get_record('course', ['id' => $data[$coursekey]])) { + if ($courseid && $courseid > 0) { + if ($course = $DB->get_record('course', ['id' => $courseid])) { if ($data['timestart'] < $course->startdate) { $errors['timestart'] = get_string('errorbeforecoursestart', 'calendar'); } @@ -136,11 +140,15 @@ class create extends \moodleform { } } - if ($eventtype == 'course' && empty($data['courseid'])) { + if ($eventtype == 'course' && empty($courseid)) { $errors['courseid'] = get_string('selectacourse'); } - if ($eventtype == 'group' && empty($data['groupcourseid'])) { + if ($eventtype == 'group' && (!empty($courseid) && empty($data['groupid']))) { + $errors['groupcourseid'] = get_string('nogroups', 'core_group'); + } + + if ($eventtype == 'group' && empty($courseid)) { $errors['groupcourseid'] = get_string('selectacourse'); } diff --git a/calendar/classes/local/event/forms/eventtype.php b/calendar/classes/local/event/forms/eventtype.php index 59a9cd70c43..f8486875da7 100644 --- a/calendar/classes/local/event/forms/eventtype.php +++ b/calendar/classes/local/event/forms/eventtype.php @@ -53,28 +53,29 @@ trait eventtype { * @param array $eventtypes The available event types for the user */ protected function add_event_type_elements($mform, $eventtypes) { + global $CFG, $DB; $options = []; - if (isset($eventtypes['user'])) { + if (!empty($eventtypes['user'])) { $options['user'] = get_string('user'); } - if (isset($eventtypes['group'])) { + if (!empty($eventtypes['group'])) { $options['group'] = get_string('group'); } - if (isset($eventtypes['course'])) { + if (!empty($eventtypes['course'])) { $options['course'] = get_string('course'); } - if (isset($eventtypes['category'])) { + if (!empty($eventtypes['category'])) { $options['category'] = get_string('category'); } - if (isset($eventtypes['site'])) { + if (!empty($eventtypes['site'])) { $options['site'] = get_string('site'); } // If we only have one event type and it's 'user' event then don't bother // rendering the select boxes because there is no choice for the user to // make. - if (count(array_keys($eventtypes)) == 1 && isset($eventtypes['user'])) { + if (!empty($eventtypes['user']) && count($options) == 1) { $mform->addElement('hidden', 'eventtype'); $mform->setType('eventtype', PARAM_TEXT); $mform->setDefault('eventtype', 'user'); @@ -87,9 +88,9 @@ trait eventtype { $mform->addElement('select', 'eventtype', get_string('eventkind', 'calendar'), $options); } - if (isset($eventtypes['category'])) { + if (!empty($eventtypes['category'])) { $categoryoptions = []; - foreach ($eventtypes['category'] as $id => $category) { + foreach (\coursecat::make_categories_list('moodle/category:manage') as $id => $category) { $categoryoptions[$id] = $category; } @@ -97,33 +98,26 @@ trait eventtype { $mform->hideIf('categoryid', 'eventtype', 'noteq', 'category'); } - if (isset($eventtypes['course'])) { - $limit = !has_capability('moodle/calendar:manageentries', \context_system::instance()); - $mform->addElement('course', 'courseid', get_string('course'), ['limittoenrolled' => $limit]); + $showall = $CFG->calendar_adminseesall && !has_capability('moodle/calendar:manageentries', \context_system::instance()); + if (!empty($eventtypes['course'])) { + $mform->addElement('course', 'courseid', get_string('course'), ['limittoenrolled' => !$showall]); $mform->hideIf('courseid', 'eventtype', 'noteq', 'course'); } - if (isset($eventtypes['group'])) { - $options = ['limittoenrolled' => true]; - // Exclude courses without group. - if (isset($eventtypes['course']) && isset($eventtypes['groupcourses'])) { - $options['exclude'] = array_diff(array_keys($eventtypes['course']), - array_keys($eventtypes['groupcourses'])); - } - - $mform->addElement('course', 'groupcourseid', get_string('course'), $options); + if (!empty($eventtypes['group'])) { + $groups = !(empty($this->_customdata['groups'])) ? $this->_customdata['groups'] : null; + // Get the list of courses without groups to filter on the course selector. + $sql = "SELECT c.id + FROM {course} c + WHERE c.id NOT IN ( + SELECT DISTINCT courseid FROM {groups} + )"; + $coursesnogroup = $DB->get_records_sql($sql); + $mform->addElement('course', 'groupcourseid', get_string('course'), ['limittoenrolled' => !$showall, + 'exclude' => array_keys($coursesnogroup)]); $mform->hideIf('groupcourseid', 'eventtype', 'noteq', 'group'); - $groupoptions = []; - foreach ($eventtypes['group'] as $group) { - // We are formatting it this way in order to provide the javascript both - // the course and group ids so that it can enhance the form for the user. - $index = "{$group->courseid}-{$group->id}"; - $groupoptions[$index] = format_string($group->name, true, - ['context' => \context_course::instance($group->courseid)]); - } - - $mform->addElement('select', 'groupid', get_string('group'), $groupoptions); + $mform->addElement('select', 'groupid', get_string('group'), $groups); $mform->hideIf('groupid', 'eventtype', 'noteq', 'group'); // We handle the group select hide/show actions on the event_form module. } diff --git a/calendar/classes/local/event/forms/managesubscriptions.php b/calendar/classes/local/event/forms/managesubscriptions.php index 0e5a0b6df83..102761f7116 100644 --- a/calendar/classes/local/event/forms/managesubscriptions.php +++ b/calendar/classes/local/event/forms/managesubscriptions.php @@ -41,8 +41,8 @@ class managesubscriptions extends \moodleform { */ public function definition() { $mform = $this->_form; - $eventtypes = calendar_get_all_allowed_types(); - if (empty($eventtypes)) { + $eventtypes = calendar_get_allowed_event_types(); + if (in_array(true, $eventtypes, true) === false) { print_error('nopermissiontoupdatecalendar'); } @@ -100,9 +100,10 @@ class managesubscriptions extends \moodleform { $errors = parent::validation($data, $files); - $coursekey = isset($data['groupcourseid']) ? 'groupcourseid' : 'courseid'; - $eventtypes = calendar_get_all_allowed_types(); $eventtype = isset($data['eventtype']) ? $data['eventtype'] : null; + $coursekey = ($eventtype == 'group') ? 'groupcourseid' : 'courseid'; + $courseid = (!empty($data[$coursekey])) ? $data[$coursekey] : null; + $eventtypes = calendar_get_allowed_event_types($courseid); if (empty($eventtype) || !isset($eventtypes[$eventtype])) { $errors['eventtype'] = get_string('invalideventtype', 'calendar'); diff --git a/calendar/classes/local/event/mappers/create_update_form_mapper.php b/calendar/classes/local/event/mappers/create_update_form_mapper.php index 91c1435ed1d..032d83930b1 100644 --- a/calendar/classes/local/event/mappers/create_update_form_mapper.php +++ b/calendar/classes/local/event/mappers/create_update_form_mapper.php @@ -58,7 +58,7 @@ class create_update_form_mapper implements create_update_form_mapper_interface { if ($legacyevent->eventtype == 'group') { // Set up the correct value for the to display on the form. - $data->groupid = "{$legacyevent->courseid}-{$legacyevent->groupid}"; + $data->groupid = $legacyevent->groupid; $data->groupcourseid = $legacyevent->courseid; } if ($legacyevent->eventtype == 'course') { @@ -93,12 +93,8 @@ class create_update_form_mapper implements create_update_form_mapper_interface { $properties->courseid = $data->groupcourseid; unset($properties->groupcourseid); } - - // Pull the group id back out of the value. The form saves the value - // as "-" to allow the javascript to work correctly. if (isset($data->groupid)) { - list($courseid, $groupid) = explode('-', $data->groupid); - $properties->groupid = $groupid; + $properties->groupid = $data->groupid; } } else { // Default course id if none is set. diff --git a/calendar/classes/local/event/strategies/raw_event_retrieval_strategy.php b/calendar/classes/local/event/strategies/raw_event_retrieval_strategy.php index d4775ee8eb5..5b863b2ccae 100644 --- a/calendar/classes/local/event/strategies/raw_event_retrieval_strategy.php +++ b/calendar/classes/local/event/strategies/raw_event_retrieval_strategy.php @@ -170,7 +170,7 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter $subqueryconditions = []; // Get the user's courses. Otherwise, get the default courses being shown by the calendar. - $usercourses = calendar_get_default_courses(); + $usercourses = calendar_get_default_courses(null, 'id, category, groupmode, groupmodeforce'); // Set calendar filters. list($usercourses, $usergroups, $user) = calendar_set_filters($usercourses, true); diff --git a/calendar/externallib.php b/calendar/externallib.php index 10fa760652c..80e60cd8667 100644 --- a/calendar/externallib.php +++ b/calendar/externallib.php @@ -865,15 +865,31 @@ class core_calendar_external extends external_api { self::validate_context($context); parse_str($params['formdata'], $data); + $eventtype = isset($data['eventtype']) ? $data['eventtype'] : null; + $coursekey = ($eventtype == 'group') ? 'groupcourseid' : 'courseid'; + $courseid = (!empty($data[$coursekey])) ? $data[$coursekey] : null; + $editoroptions = \core_calendar\local\event\forms\create::build_editor_options($context); + $formoptions = ['editoroptions' => $editoroptions, 'courseid' => $courseid]; + if ($courseid) { + require_once($CFG->libdir . '/grouplib.php'); + $groupcoursedata = groups_get_course_data($courseid); + if (!empty($groupcoursedata->groups)) { + $formoptions['groups'] = []; + foreach ($groupcoursedata->groups as $groupid => $groupdata) { + $formoptions['groups'][$groupid] = $groupdata->name; + } + } + } + if (!empty($data['id'])) { $eventid = clean_param($data['id'], PARAM_INT); $legacyevent = calendar_event::load($eventid); $legacyevent->count_repeats(); - $formoptions = ['event' => $legacyevent]; + $formoptions['event'] = $legacyevent; $mform = new update_event_form(null, $formoptions, 'post', '', null, true, $data); } else { $legacyevent = null; - $mform = new create_event_form(null, null, 'post', '', null, true, $data); + $mform = new create_event_form(null, $formoptions, 'post', '', null, true, $data); } if ($validateddata = $mform->get_data()) { diff --git a/calendar/lib.php b/calendar/lib.php index 0f684dd9c4a..ba7617db319 100644 --- a/calendar/lib.php +++ b/calendar/lib.php @@ -1070,7 +1070,7 @@ class calendar_information { $category = (\coursecat::get($course->category, MUST_EXIST, true))->get_db_record(); } else if (!empty($categoryid)) { $course = get_site(); - $courses = calendar_get_default_courses(); + $courses = calendar_get_default_courses(null, 'id, category, groupmode, groupmodeforce'); // Filter available courses to those within this category or it's children. $ids = [$categoryid]; @@ -1084,7 +1084,7 @@ class calendar_information { $calendar->context = context_coursecat::instance($categoryid); } else { $course = get_site(); - $courses = calendar_get_default_courses(); + $courses = calendar_get_default_courses(null, 'id, category, groupmode, groupmodeforce'); $category = null; $calendar->context = context_system::instance(); @@ -3506,18 +3506,18 @@ function calendar_get_view(\calendar_information $calendar, $view, $includenavig */ function calendar_output_fragment_event_form($args) { global $CFG, $OUTPUT, $USER; - + require_once($CFG->libdir . '/grouplib.php'); $html = ''; $data = []; $eventid = isset($args['eventid']) ? clean_param($args['eventid'], PARAM_INT) : null; $starttime = isset($args['starttime']) ? clean_param($args['starttime'], PARAM_INT) : null; - $courseid = isset($args['courseid']) ? clean_param($args['courseid'], PARAM_INT) : null; + $courseid = (isset($args['courseid']) && $args['courseid'] != SITEID) ? clean_param($args['courseid'], PARAM_INT) : null; $categoryid = isset($args['categoryid']) ? clean_param($args['categoryid'], PARAM_INT) : null; $event = null; $hasformdata = isset($args['formdata']) && !empty($args['formdata']); $context = \context_user::instance($USER->id); $editoroptions = \core_calendar\local\event\forms\create::build_editor_options($context); - $formoptions = ['editoroptions' => $editoroptions]; + $formoptions = ['editoroptions' => $editoroptions, 'courseid' => $courseid]; $draftitemid = 0; if ($hasformdata) { @@ -3532,6 +3532,13 @@ function calendar_output_fragment_event_form($args) { } if (is_null($eventid)) { + if (!empty($courseid)) { + $groupcoursedata = groups_get_course_data($courseid); + $formoptions['groups'] = []; + foreach ($groupcoursedata->groups as $groupid => $groupdata) { + $formoptions['groups'][$groupid] = $groupdata->name; + } + } $mform = new \core_calendar\local\event\forms\create( null, $formoptions, @@ -3543,16 +3550,19 @@ function calendar_output_fragment_event_form($args) { ); // Let's check first which event types user can add. - calendar_get_allowed_types($allowed, $courseid); + $eventtypes = calendar_get_allowed_event_types($courseid); // If the user is on course context and is allowed to add course events set the event type default to course. - if ($courseid != SITEID && !empty($allowed->courses)) { + if ($courseid != SITEID && !empty($eventtypes['course'])) { $data['eventtype'] = 'course'; $data['courseid'] = $courseid; $data['groupcourseid'] = $courseid; - } else if (!empty($categoryid) && !empty($allowed->category)) { + } else if (!empty($categoryid) && !empty($eventtypes['category'])) { $data['eventtype'] = 'category'; $data['categoryid'] = $categoryid; + } else if (!empty($groupcoursedata) && !empty($eventtypes['group'])) { + $data['groupcourseid'] = $courseid; + $data['groups'] = $groupcoursedata->groups; } $mform->set_data($data); } else { @@ -3562,6 +3572,15 @@ function calendar_output_fragment_event_form($args) { $data = array_merge((array) $eventdata, $data); $event->count_repeats(); $formoptions['event'] = $event; + + if (!empty($event->courseid)) { + $groupcoursedata = groups_get_course_data($event->courseid); + $formoptions['groups'] = []; + foreach ($groupcoursedata->groups as $groupid => $groupdata) { + $formoptions['groups'][$groupid] = $groupdata->name; + } + } + $data['description']['text'] = file_prepare_draft_area( $draftitemid, $event->context->id, @@ -3690,3 +3709,163 @@ function calendar_is_valid_eventtype($type) { ]; return in_array($type, $validtypes); } + +/** + * Get event types the user can create event based on categories, courses and groups + * the logged in user belongs to. + * + * @param int|null $courseid The course id. + * @return array The array of allowed types. + */ +function calendar_get_allowed_event_types(int $courseid = null) { + global $DB, $CFG, $USER; + + $types = [ + 'user' => false, + 'site' => false, + 'course' => false, + 'group' => false, + 'category' => false + ]; + + if (!empty($courseid) && $courseid != SITEID) { + $context = \context_course::instance($courseid); + $groups = groups_get_all_groups($courseid); + + $types['user'] = has_capability('moodle/calendar:manageownentries', $context); + + if (has_capability('moodle/calendar:manageentries', $context) || !empty($CFG->calendar_adminseesall)) { + $types['course'] = true; + + $types['group'] = (!empty($groups) && has_capability('moodle/site:accessallgroups', $context)) + || array_filter($groups, function($group) use ($USER) { + return groups_is_member($group->id); + }); + } else if (has_capability('moodle/calendar:managegroupentries', $context)) { + $types['group'] = (!empty($groups) && has_capability('moodle/site:accessallgroups', $context)) + || array_filter($groups, function($group) use ($USER) { + return groups_is_member($group->id); + }); + } + } + + if (has_capability('moodle/calendar:manageentries', \context_course::instance(SITEID))) { + $types['site'] = true; + } + + if (has_capability('moodle/calendar:manageownentries', \context_system::instance())) { + $types['user'] = true; + } + if (coursecat::has_manage_capability_on_any()) { + $types['category'] = true; + } + + // We still don't know if the user can create group and course events, so iterate over the courses to find out + // if the user has capabilities in one of the courses. + if ($types['course'] == false || $types['group'] == false) { + if ($CFG->calendar_adminseesall && has_capability('moodle/calendar:manageentries', context_system::instance())) { + $sql = "SELECT c.id, " . context_helper::get_preload_record_columns_sql('ctx') . " + FROM {course} c + JOIN {context} ctx ON ctx.contextlevel = ? AND ctx.instanceid = c.id + WHERE c.id IN ( + SELECT DISTINCT courseid FROM {groups} + )"; + $courseswithgroups = $DB->get_recordset_sql($sql, [CONTEXT_COURSE]); + foreach ($courseswithgroups as $course) { + context_helper::preload_from_record($course); + $context = context_course::instance($course->id); + + if (has_capability('moodle/calendar:manageentries', $context)) { + if (has_any_capability(['moodle/site:accessallgroups', 'moodle/calendar:managegroupentries'], $context)) { + // The user can manage group entries or access any group. + $types['group'] = true; + $types['course'] = true; + break; + } + } + } + $courseswithgroups->close(); + + if (false === $types['course']) { + // Course is still not confirmed. There may have been no courses with a group in them. + $ctxfields = context_helper::get_preload_record_columns_sql('ctx'); + $sql = "SELECT + c.id, c.visible, {$ctxfields} + FROM {course} + JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)"; + $params = [ + 'contextlevel' => CONTEXT_COURSE, + ]; + $courses = $DB->get_recordset_sql($sql, $params); + foreach ($courses as $course) { + context_helper::preload_from_record($course); + $context = context_course::instance($course->id); + if (has_capability('moodle/calendar:manageentries', $context)) { + $types['course'] = true; + break; + } + } + $courses->close(); + } + + } else { + $courses = calendar_get_default_courses(null, 'id'); + if (empty($courses)) { + return $types; + } + + $courseids = array_map(function($c) { + return $c->id; + }, $courses); + + list($insql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED); + $sql = "SELECT c.id, " . context_helper::get_preload_record_columns_sql('ctx') . " + FROM {course} c + JOIN {context} ctx ON ctx.contextlevel = :contextlevel AND ctx.instanceid = c.id + WHERE c.id $insql + AND c.id IN (SELECT DISTINCT courseid FROM {groups})"; + $params['contextlevel'] = CONTEXT_COURSE; + $courseswithgroups = $DB->get_recordset_sql($sql, $params); + foreach ($courseswithgroups as $coursewithgroup) { + context_helper::preload_from_record($coursewithgroup); + $context = context_course::instance($coursewithgroup->id); + + if (has_capability('moodle/calendar:manageentries', $context)) { + $types['course'] = true; + $types['group'] = (!empty(groups_get_all_groups($coursewithgroup->id, $USER->id)) + && has_capability('moodle/calendar:managegroupentries', $context)); + } + + // Okay, course and group event types are allowed, no need to keep the loop iteration. + if ($types['course'] == true && $types['group'] == true) { + break; + } + } + $courseswithgroups->close(); + + if (false === $types['course']) { + list($insql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED); + $contextsql = "SELECT c.id, " . context_helper::get_preload_record_columns_sql('ctx') . " + FROM {course} c + JOIN {context} ctx ON ctx.contextlevel = :contextlevel AND ctx.instanceid = c.id + WHERE c.id $insql + GROUP BY c.id, ctx.id"; + $params['contextlevel'] = CONTEXT_COURSE; + $contextrecords = $DB->get_recordset_sql($contextsql, $params); + foreach ($contextrecords as $course) { + context_helper::preload_from_record($course); + $coursecontext = context_course::instance($course->id); + if (has_capability('moodle/calendar:manageentries', $coursecontext) + && ($courseid == $course->id || empty($courseid))) { + $types['course'] = true; + break; + } + } + $contextrecords->close(); + } + + } + } + + return $types; +} diff --git a/calendar/managesubscriptions.php b/calendar/managesubscriptions.php index 75ddc09f192..87289a6c9be 100644 --- a/calendar/managesubscriptions.php +++ b/calendar/managesubscriptions.php @@ -61,7 +61,7 @@ if (!calendar_user_can_add_event($course)) { print_error('errorcannotimport', 'calendar'); } -$form = new \core_calendar\local\event\forms\managesubscriptions(); +$form = new \core_calendar\local\event\forms\managesubscriptions(null, ['courseid' => $course->id]); $form->set_data(array( 'course' => $course->id )); @@ -105,26 +105,26 @@ if (!empty($formdata)) { } } -$types = calendar_get_all_allowed_types(); +$types = calendar_get_allowed_event_types($courseid); $searches = []; $params = []; $usedefaultfilters = true; -if (!empty($courseid) && $courseid == SITEID && isset($types['site'])) { +if (!empty($courseid) && $courseid == SITEID && !empty($types['site'])) { $searches[] = "(eventtype = 'site')"; $searches[] = "(eventtype = 'user' AND userid = :userid)"; $params['userid'] = $USER->id; $usedefaultfilters = false; } -if (!empty($courseid) && isset($types['course']) && array_key_exists($courseid, $types['course'])) { +if (!empty($courseid) && !empty($types['course'])) { $searches[] = "((eventtype = 'course' OR eventtype = 'group') AND courseid = :courseid)"; $params += ['courseid' => $courseid]; $usedefaultfilters = false; } -if (!empty($categoryid) && isset($types['category']) && array_key_exists($categoryid, $types['category'])) { +if (!empty($categoryid) && !empty($types['category'])) { $searches[] = "(eventtype = 'category' AND categoryid = :categoryid)"; $params += ['categoryid' => $categoryid]; $usedefaultfilters = false; @@ -134,19 +134,27 @@ if ($usedefaultfilters) { $searches[] = "(eventtype = 'user' AND userid = :userid)"; $params['userid'] = $USER->id; - if (isset($types['site'])) { + if (!empty($types['site'])) { $searches[] = "(eventtype = 'site' AND courseid = :siteid)"; $params += ['siteid' => SITEID]; } - if (isset($types['course'])) { - list($courseinsql, $courseparams) = $DB->get_in_or_equal(array_keys($types['course']), SQL_PARAMS_NAMED, 'course'); - $searches[] = "((eventtype = 'course' OR eventtype = 'group') AND courseid {$courseinsql})"; - $params += $courseparams; + if (!empty($types['course'])) { + $courses = calendar_get_default_courses(null, 'id', true); + if (!empty($courses)) { + $courseids = array_map(function ($c) { + return $c->id; + }, $courses); + + list($courseinsql, $courseparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'course'); + $searches[] = "((eventtype = 'course' OR eventtype = 'group') AND courseid {$courseinsql})"; + $params += $courseparams; + } } - if (isset($types['category'])) { - list($categoryinsql, $categoryparams) = $DB->get_in_or_equal(array_keys($types['category']), SQL_PARAMS_NAMED, 'category'); + if (!empty($types['category'])) { + list($categoryinsql, $categoryparams) = $DB->get_in_or_equal( + array_keys(\coursecat::make_categories_list('moodle/category:manage')), SQL_PARAMS_NAMED, 'category'); $searches[] = "(eventtype = 'category' AND categoryid {$categoryinsql})"; $params += $categoryparams; } diff --git a/calendar/tests/externallib_test.php b/calendar/tests/externallib_test.php index 9fcf147e005..a63e3b1deac 100644 --- a/calendar/tests/externallib_test.php +++ b/calendar/tests/externallib_test.php @@ -1981,12 +1981,12 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase { $this->resetAfterTest(true); $this->setUser($user); - $this->expectException('moodle_exception'); - - external_api::clean_returnvalue( + $result = external_api::clean_returnvalue( core_calendar_external::submit_create_update_form_returns(), core_calendar_external::submit_create_update_form($querystring) ); + + $this->assertTrue($result['validationerror']); } /** @@ -2019,7 +2019,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase { 'minute' => 0, ], 'eventtype' => 'group', - 'groupid' => "{$course->id}-{$group->id}", // The form format. + 'groupid' => $group->id, 'groupcourseid' => $course->id, 'description' => [ 'text' => '', @@ -2091,7 +2091,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase { 'minute' => 0, ], 'eventtype' => 'group', - 'groupid' => "{$course->id}-{$group->id}", // The form format. + 'groupid' => $group->id, 'groupcourseid' => $course->id, 'description' => [ 'text' => '', @@ -2164,7 +2164,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase { 'minute' => 0, ], 'eventtype' => 'group', - 'groupid' => "{$course->id}-{$group->id}", // The form format. + 'groupid' => $group->id, 'groupcourseid' => $course->id, 'description' => [ 'text' => '', @@ -2237,7 +2237,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase { 'minute' => 0, ], 'eventtype' => 'group', - 'groupid' => "{$course->id}-{$group->id}", // The form format. + 'groupid' => $group->id, 'groupcourseid' => $course->id, 'description' => [ 'text' => '', diff --git a/calendar/tests/lib_test.php b/calendar/tests/lib_test.php index 6c6e7536ceb..75e6c526fa3 100644 --- a/calendar/tests/lib_test.php +++ b/calendar/tests/lib_test.php @@ -757,4 +757,162 @@ class core_calendar_lib_testcase extends advanced_testcase { $this->assertEquals($event->id, $data->events[0]->id); } + + public function test_calendar_get_allowed_event_types_no_types() { + $generator = $this->getDataGenerator(); + $user = $generator->create_user(); + $systemcontext = context_system::instance(); + $sitecontext = context_course::instance(SITEID); + $roleid = $generator->create_role(); + $generator->role_assign($roleid, $user->id, $systemcontext->id); + $generator->role_assign($roleid, $user->id, $sitecontext->id); + $this->setUser($user); + assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $sitecontext, true); + assign_capability('moodle/calendar:manageownentries', CAP_PROHIBIT, $roleid, $systemcontext, true); + $types = calendar_get_allowed_event_types(); + foreach ($types as $allowed) { + $this->assertFalse($allowed); + } + } + + public function test_calendar_get_allowed_event_types_user() { + $generator = $this->getDataGenerator(); + $user = $generator->create_user(); + $context = context_system::instance(); + $roleid = $generator->create_role(); + $generator->role_assign($roleid, $user->id, $context->id); + $this->setUser($user); + assign_capability('moodle/calendar:manageownentries', CAP_ALLOW, $roleid, $context, true); + $types = calendar_get_allowed_event_types(); + $this->assertTrue($types['user']); + assign_capability('moodle/calendar:manageownentries', CAP_PROHIBIT, $roleid, $context, true); + $types = calendar_get_allowed_event_types(); + $this->assertFalse($types['user']); + } + + public function test_calendar_get_allowed_event_types_site() { + $generator = $this->getDataGenerator(); + $user = $generator->create_user(); + $context = context_course::instance(SITEID); + $roleid = $generator->create_role(); + $generator->role_assign($roleid, $user->id, $context->id); + $this->setUser($user); + assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true); + $types = calendar_get_allowed_event_types(); + $this->assertTrue($types['site']); + assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true); + $types = calendar_get_allowed_event_types(); + $this->assertFalse($types['site']); + } + + public function test_calendar_get_allowed_event_types_course() { + $generator = $this->getDataGenerator(); + $user = $generator->create_user(); + $course1 = $generator->create_course(); // Has capability. + $course2 = $generator->create_course(); // Doesn't have capability. + $course3 = $generator->create_course(); // Not enrolled. + $context1 = context_course::instance($course1->id); + $context2 = context_course::instance($course2->id); + $context3 = context_course::instance($course3->id); + $roleid = $generator->create_role(); + $contexts = [$context1, $context2, $context3]; + $enrolledcourses = [$course1, $course2]; + foreach ($enrolledcourses as $course) { + $generator->enrol_user($user->id, $course->id, 'student'); + } + foreach ($contexts as $context) { + $generator->role_assign($roleid, $user->id, $context->id); + } + $this->setUser($user); + assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context1, true); + assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context2, true); + // The user only has the correct capability in course 1 so that is the only + // one that should be in the results. + $types = calendar_get_allowed_event_types($course1->id); + $this->assertTrue($types['course']); + assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context1, true); + // The user only now has the correct capability in both course 1 and 2 so we + // expect both to be in the results. + $types = calendar_get_allowed_event_types($course1->id); + $this->assertFalse($types['course']); + } + public function test_calendar_get_allowed_event_types_group_no_groups() { + $generator = $this->getDataGenerator(); + $user = $generator->create_user(); + $course = $generator->create_course(); + $context = context_course::instance($course->id); + $roleid = $generator->create_role(); + $generator->enrol_user($user->id, $course->id, 'student'); + $generator->role_assign($roleid, $user->id, $context->id); + $this->setUser($user); + assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true); + // The user has the correct capability in the course but there are + // no groups so we shouldn't see a group type. + $types = calendar_get_allowed_event_types($course->id); + $this->assertTrue($types['course']); + } + public function test_calendar_get_allowed_event_types_group_no_acces_to_diff_groups() { + $generator = $this->getDataGenerator(); + $user = $generator->create_user(); + $course = $generator->create_course(); + $context = context_course::instance($course->id); + $roleid = $generator->create_role(); + $generator->enrol_user($user->id, $course->id, 'student'); + $generator->role_assign($roleid, $user->id, $context->id); + $this->setUser($user); + assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true); + assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $roleid, $context, true); + // The user has the correct capability in the course but they aren't a member + // of any of the groups and don't have the accessallgroups capability. + $types = calendar_get_allowed_event_types($course->id); + $this->assertTrue($types['course']); + $this->assertFalse($types['group']); + } + public function test_calendar_get_allowed_event_types_group_access_all_groups() { + $generator = $this->getDataGenerator(); + $user = $generator->create_user(); + $course1 = $generator->create_course(); + $course2 = $generator->create_course(); + $generator->create_group(array('courseid' => $course1->id)); + $generator->create_group(array('courseid' => $course2->id)); + $context1 = context_course::instance($course1->id); + $context2 = context_course::instance($course2->id); + $roleid = $generator->create_role(); + $generator->enrol_user($user->id, $course1->id, 'student'); + $generator->enrol_user($user->id, $course2->id, 'student'); + $generator->role_assign($roleid, $user->id, $context1->id); + $generator->role_assign($roleid, $user->id, $context2->id); + $this->setUser($user); + assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context1, true); + assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context2, true); + assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $context1, true); + assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $context2, true); + // The user has the correct capability in the course and has + // the accessallgroups capability. + $types = calendar_get_allowed_event_types($course1->id); + $this->assertTrue($types['group']); + } + public function test_calendar_get_allowed_event_types_group_no_access_all_groups() { + $generator = $this->getDataGenerator(); + $user = $generator->create_user(); + $course = $generator->create_course(); + $context = context_course::instance($course->id); + $group1 = $generator->create_group(array('courseid' => $course->id)); + $group2 = $generator->create_group(array('courseid' => $course->id)); + $roleid = $generator->create_role(); + $generator->enrol_user($user->id, $course->id, 'student'); + $generator->role_assign($roleid, $user->id, $context->id); + $generator->create_group_member(array('groupid' => $group1->id, 'userid' => $user->id)); + $generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user->id)); + $this->setUser($user); + assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $roleid, $context, true); + // The user has the correct capability in the course but can't access + // groups that they are not a member of. + $types = calendar_get_allowed_event_types($course->id); + $this->assertFalse($types['group']); + assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true); + assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $context, true); + $types = calendar_get_allowed_event_types($course->id); + $this->assertTrue($types['group']); + } } diff --git a/lib/db/services.php b/lib/db/services.php index 976108971c7..e74a2c19211 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -764,6 +764,7 @@ $functions = array( 'classpath' => 'group/externallib.php', 'description' => 'Returns all groups in specified course.', 'type' => 'read', + 'ajax' => true, 'capabilities' => 'moodle/course:managegroups' ), 'core_group_get_course_user_groups' => array(