Commit b9fd5164 authored by Andrew Nicols's avatar Andrew Nicols
Browse files

MDL-60361 calendar: Support all event types for calendar subs

parent 0df5c743
......@@ -21,26 +21,30 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package calendar
*/
namespace core_calendar\local\event\forms;
if (!defined('MOODLE_INTERNAL')) {
die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
}
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
require_once($CFG->libdir . '/formslib.php');
/**
* Form for adding a subscription to a Moodle course calendar.
* @copyright 2012 Jonathan Harker
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class calendar_addsubscription_form extends moodleform {
class managesubscriptions extends \moodleform {
use eventtype;
/**
* Defines the form used to add calendar subscriptions.
*/
public function definition() {
$mform = $this->_form;
$courseid = optional_param('course', 0, PARAM_INT);
$eventtypes = calendar_get_all_allowed_types();
if (empty($eventtypes)) {
print_error('nopermissiontoupdatecalendar');
}
$mform->addElement('header', 'addsubscriptionform', get_string('importcalendarheading', 'calendar'));
......@@ -77,24 +81,10 @@ class calendar_addsubscription_form extends moodleform {
$mform->disabledIf('url', 'importfrom', 'eq', CALENDAR_IMPORT_FROM_FILE);
$mform->disabledIf('importfile', 'importfrom', 'eq', CALENDAR_IMPORT_FROM_URL);
// Eventtype: 0 = user, 1 = global, anything else = course ID.
list($choices, $groups) = calendar_get_eventtype_choices($courseid);
$mform->addElement('select', 'eventtype', get_string('eventkind', 'calendar'), $choices);
$mform->addRule('eventtype', get_string('required'), 'required');
$mform->setType('eventtype', PARAM_ALPHA);
if (!empty($groups) and is_array($groups)) {
$groupoptions = array();
foreach ($groups as $group) {
$groupoptions[$group->id] = $group->name;
}
$mform->addElement('select', 'groupid', get_string('typegroup', 'calendar'), $groupoptions);
$mform->setType('groupid', PARAM_INT);
$mform->disabledIf('groupid', 'eventtype', 'noteq', 'group');
}
// Add the select elements for the available event types.
$this->add_event_type_elements($mform, $eventtypes);
$mform->addElement('hidden', 'course');
$mform->setType('course', PARAM_INT);
// Eventtype: 0 = user, 1 = global, anything else = course ID.
$mform->addElement('submit', 'add', get_string('add'));
}
......@@ -110,6 +100,14 @@ class calendar_addsubscription_form 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;
if (empty($eventtype) || !isset($eventtypes[$eventtype])) {
$errors['eventtype'] = get_string('invalideventtype', 'calendar');
}
if ($data['importfrom'] == CALENDAR_IMPORT_FROM_FILE) {
if (empty($data['importfile'])) {
$errors['importfile'] = get_string('errorrequiredurlorfile', 'calendar');
......@@ -117,7 +115,7 @@ class calendar_addsubscription_form extends moodleform {
// Make sure the file area is not empty and contains only one file.
$draftitemid = $data['importfile'];
$fs = get_file_storage();
$usercontext = context_user::instance($USER->id);
$usercontext = \context_user::instance($USER->id);
$files = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id DESC', false);
if (count($files) !== 1) {
$errors['importfile'] = get_string('errorrequiredurlorfile', 'calendar');
......@@ -140,7 +138,7 @@ class calendar_addsubscription_form extends moodleform {
public function definition_after_data() {
$mform =& $this->_form;
$mform->applyFilter('url', 'calendar_addsubscription_form::strip_webcal');
$mform->applyFilter('url', static::class . '::strip_webcal');
$mform->applyFilter('url', 'trim');
}
......
......@@ -2580,12 +2580,36 @@ function calendar_get_eventtype_choices($courseid) {
function calendar_add_subscription($sub) {
global $DB, $USER, $SITE;
// Undo the form definition work around to allow us to have two different
// course selectors present depending on which event type the user selects.
if (!empty($sub->groupcourseid)) {
$sub->courseid = $sub->groupcourseid;
unset($sub->groupcourseid);
}
// Pull the group id back out of the value. The form saves the value
// as "<courseid>-<groupid>" to allow the javascript to work correctly.
if (!empty($sub->groupid)) {
list($courseid, $groupid) = explode('-', $sub->groupid);
$sub->courseid = $courseid;
$sub->groupid = $groupid;
}
// Default course id if none is set.
if (empty($sub->courseid)) {
if ($sub->eventtype === 'site') {
$sub->courseid = SITEID;
} else {
$sub->courseid = 0;
}
}
if ($sub->eventtype === 'site') {
$sub->courseid = $SITE->id;
} else if ($sub->eventtype === 'group' || $sub->eventtype === 'course') {
$sub->courseid = $sub->course;
$sub->courseid = $sub->courseid;
} else if ($sub->eventtype === 'category') {
$sub->categoryid = $sub->category;
$sub->categoryid = $sub->categoryid;
} else {
// User events.
$sub->courseid = 0;
......@@ -2605,8 +2629,25 @@ function calendar_add_subscription($sub) {
// Trigger event, calendar subscription added.
$eventparams = array('objectid' => $sub->id,
'context' => calendar_get_calendar_context($sub),
'other' => array('eventtype' => $sub->eventtype, 'courseid' => $sub->courseid)
'other' => array(
'eventtype' => $sub->eventtype,
)
);
switch ($sub->eventtype) {
case 'category':
$eventparams['other']['categoryid'] = $sub->categoryid;
break;
case 'course':
$eventparams['other']['courseid'] = $sub->courseid;
break;
case 'group':
$eventparams['other']['courseid'] = $sub->courseid;
$eventparams['other']['groupid'] = $sub->groupid;
break;
default:
$eventparams['other']['courseid'] = $sub->courseid;
}
$event = \core\event\calendar_subscription_created::create($eventparams);
$event->trigger();
return $id;
......@@ -2624,13 +2665,13 @@ function calendar_add_subscription($sub) {
* Add an iCalendar event to the Moodle calendar.
*
* @param stdClass $event The RFC-2445 iCalendar event
* @param int $courseid The course ID
* @param int $unused Deprecated
* @param int $subscriptionid The iCalendar subscription ID
* @param string $timezone The X-WR-TIMEZONE iCalendar property if provided
* @throws dml_exception A DML specific exception is thrown for invalid subscriptionids.
* @return int Code: CALENDAR_IMPORT_EVENT_UPDATED = updated, CALENDAR_IMPORT_EVENT_INSERTED = inserted, 0 = error
*/
function calendar_add_icalendar_event($event, $courseid, $subscriptionid, $timezone='UTC') {
function calendar_add_icalendar_event($event, $unused = null, $subscriptionid, $timezone='UTC') {
global $DB;
// Probably an unsupported X-MICROSOFT-CDO-BUSYSTATUS event.
......@@ -2703,6 +2744,7 @@ function calendar_add_icalendar_event($event, $courseid, $subscriptionid, $timez
$eventrecord->userid = $sub->userid;
$eventrecord->groupid = $sub->groupid;
$eventrecord->courseid = $sub->courseid;
$eventrecord->categoryid = $sub->categoryid;
$eventrecord->eventtype = $sub->eventtype;
if ($updaterecord = $DB->get_record('event', array('uuid' => $eventrecord->uuid,
......@@ -2783,8 +2825,24 @@ function calendar_delete_subscription($subscription) {
// Trigger event, calendar subscription deleted.
$eventparams = array('objectid' => $subscription->id,
'context' => calendar_get_calendar_context($subscription),
'other' => array('courseid' => $subscription->courseid)
'other' => array(
'eventtype' => $subscription->eventtype,
)
);
switch ($subscription->eventtype) {
case 'category':
$eventparams['other']['categoryid'] = $subscription->categoryid;
break;
case 'course':
$eventparams['other']['courseid'] = $subscription->courseid;
break;
case 'group':
$eventparams['other']['courseid'] = $subscription->courseid;
$eventparams['other']['groupid'] = $subscription->groupid;
break;
default:
$eventparams['other']['courseid'] = $subscription->courseid;
}
$event = \core\event\calendar_subscription_deleted::create($eventparams);
$event->trigger();
}
......@@ -2823,7 +2881,7 @@ function calendar_get_icalendar($url) {
* @param int $subscriptionid The subscription ID.
* @return string A log of the import progress, including errors.
*/
function calendar_import_icalendar_events($ical, $courseid, $subscriptionid = null) {
function calendar_import_icalendar_events($ical, $unused = null, $subscriptionid = null) {
global $DB;
$return = '';
......@@ -2850,7 +2908,7 @@ function calendar_import_icalendar_events($ical, $courseid, $subscriptionid = nu
$return = '';
foreach ($ical->components['VEVENT'] as $event) {
$res = calendar_add_icalendar_event($event, $courseid, $subscriptionid, $timezone);
$res = calendar_add_icalendar_event($event, null, $subscriptionid, $timezone);
switch ($res) {
case CALENDAR_IMPORT_EVENT_UPDATED:
$updatecount++;
......@@ -2900,7 +2958,7 @@ function calendar_update_subscription_events($subscriptionid) {
}
$ical = calendar_get_icalendar($sub->url);
$return = calendar_import_icalendar_events($ical, $sub->courseid, $subscriptionid);
$return = calendar_import_icalendar_events($ical, null, $subscriptionid);
$sub->lastupdated = time();
calendar_update_subscription($sub);
......@@ -2935,8 +2993,24 @@ function calendar_update_subscription($subscription) {
$eventparams = array('userid' => $subscription->userid,
'objectid' => $subscription->id,
'context' => calendar_get_calendar_context($subscription),
'other' => array('eventtype' => $subscription->eventtype, 'courseid' => $subscription->courseid)
'other' => array(
'eventtype' => $subscription->eventtype,
)
);
switch ($subscription->eventtype) {
case 'category':
$eventparams['other']['categoryid'] = $subscription->categoryid;
break;
case 'course':
$eventparams['other']['courseid'] = $subscription->courseid;
break;
case 'group':
$eventparams['other']['courseid'] = $subscription->courseid;
$eventparams['other']['groupid'] = $subscription->groupid;
break;
default:
$eventparams['other']['courseid'] = $subscription->courseid;
}
$event = \core\event\calendar_subscription_updated::create($eventparams);
$event->trigger();
}
......@@ -2958,9 +3032,14 @@ function calendar_can_edit_subscription($subscriptionorid) {
$allowed = new \stdClass;
$courseid = $subscription->courseid;
$categoryid = $subscription->categoryid;
$groupid = $subscription->groupid;
$category = null;
calendar_get_allowed_types($allowed, $courseid);
if (!empty($categoryid)) {
$category = \coursecat::get($categoryid);
}
calendar_get_allowed_types($allowed, $courseid, null, $category);
switch ($subscription->eventtype) {
case 'user':
return $allowed->user;
......@@ -2970,6 +3049,12 @@ function calendar_can_edit_subscription($subscriptionorid) {
} else {
return false;
}
case 'category':
if (isset($allowed->categories[$categoryid])) {
return $allowed->categories[$categoryid];
} else {
return false;
}
case 'site':
return $allowed->site;
case 'group':
......
......@@ -26,10 +26,10 @@ require_once('../config.php');
require_once($CFG->libdir.'/bennu/bennu.inc.php');
require_once($CFG->dirroot.'/course/lib.php');
require_once($CFG->dirroot.'/calendar/lib.php');
require_once($CFG->dirroot.'/calendar/managesubscriptions_form.php');
// Required use.
$courseid = optional_param('course', SITEID, PARAM_INT);
$courseid = optional_param('course', null, PARAM_INT);
$categoryid = optional_param('category', null, PARAM_INT);
// Used for processing subscription actions.
$subscriptionid = optional_param('id', 0, PARAM_INT);
$pollinterval = optional_param('pollinterval', 0, PARAM_INT);
......@@ -39,6 +39,9 @@ $url = new moodle_url('/calendar/managesubscriptions.php');
if ($courseid != SITEID) {
$url->param('course', $courseid);
}
if ($categoryid) {
$url->param('categoryid', $categoryid);
}
navigation_node::override_active_url(new moodle_url('/calendar/view.php', array('view' => 'month')));
$PAGE->set_url($url);
$PAGE->set_pagelayout('admin');
......@@ -58,7 +61,7 @@ if (!calendar_user_can_add_event($course)) {
print_error('errorcannotimport', 'calendar');
}
$form = new calendar_addsubscription_form(null);
$form = new \core_calendar\local\event\forms\managesubscriptions();
$form->set_data(array(
'course' => $course->id
));
......@@ -75,7 +78,7 @@ if (!empty($formdata)) {
$calendar = $form->get_file_content('importfile');
$ical = new iCalendar();
$ical->unserialize($calendar);
$importresults = calendar_import_icalendar_events($ical, $courseid, $subscriptionid);
$importresults = calendar_import_icalendar_events($ical, null, $subscriptionid);
} else {
try {
$importresults = calendar_update_subscription_events($subscriptionid);
......@@ -102,11 +105,54 @@ if (!empty($formdata)) {
}
}
$sql = 'SELECT *
FROM {event_subscriptions}
WHERE courseid = :courseid
OR (courseid = 0 AND userid = :userid)';
$params = array('courseid' => $courseid, 'userid' => $USER->id);
$types = calendar_get_all_allowed_types();
$searches = [];
$params = [];
$usedefaultfilters = true;
if (!empty($courseid) && $courseid == SITEID && isset($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'])) {
$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'])) {
$searches[] = "(eventtype = 'category' AND categoryid = :categoryid)";
$params += ['categoryid' => $categoryid];
$usedefaultfilters = false;
}
if ($usedefaultfilters) {
$searches[] = "(eventtype = 'user' AND userid = :userid)";
$params['userid'] = $USER->id;
if (isset($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 (isset($types['category'])) {
list($categoryinsql, $categoryparams) = $DB->get_in_or_equal(array_keys($types['category']), SQL_PARAMS_NAMED, 'category');
$searches[] = "(eventtype = 'category' AND categoryid {$categoryinsql})";
$params += $categoryparams;
}
}
$sql = "SELECT * FROM {event_subscriptions} WHERE " . implode(' OR ', $searches);;
$subscriptions = $DB->get_records_sql($sql, $params);
// Print title and header.
......
......@@ -283,12 +283,12 @@ class core_calendar_renderer extends plugin_renderer_base {
/**
* Renders a table containing information about calendar subscriptions.
*
* @param int $courseid
* @param int $unused
* @param array $subscriptions
* @param string $importresults
* @return string
*/
public function subscription_details($courseid, $subscriptions, $importresults = '') {
public function subscription_details($unused = null, $subscriptions, $importresults = '') {
$table = new html_table();
$table->head = array(
get_string('colcalendar', 'calendar'),
......@@ -318,7 +318,7 @@ class core_calendar_renderer extends plugin_renderer_base {
$lastupdated = userdate($sub->lastupdated, get_string('strftimedatetimeshort', 'langconfig'));
}
$cell = new html_table_cell($this->subscription_action_form($sub, $courseid));
$cell = new html_table_cell($this->subscription_action_form($sub));
$cell->colspan = 2;
$type = $sub->eventtype . 'events';
......@@ -342,10 +342,9 @@ class core_calendar_renderer extends plugin_renderer_base {
* Creates a form to perform actions on a given subscription.
*
* @param stdClass $subscription
* @param int $courseid
* @return string
*/
protected function subscription_action_form($subscription, $courseid) {
protected function subscription_action_form($subscription) {
// Assemble form for the subscription row.
$html = html_writer::start_tag('form', array('action' => new moodle_url('/calendar/managesubscriptions.php'), 'method' => 'post'));
if (empty($subscription->url)) {
......@@ -367,7 +366,6 @@ class core_calendar_renderer extends plugin_renderer_base {
$html .= html_writer::end_tag('div');
}
$html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
$html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'course', 'value' => $courseid));
$html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'id', 'value' => $subscription->id));
$html .= html_writer::start_tag('div', array('class' => 'btn-group pull-right'));
if (!empty($subscription->url)) {
......
......@@ -423,9 +423,9 @@ class core_calendar_events_testcase extends advanced_testcase {
}
/**
* Tests for calendar_subscription_added event.
* Tests for calendar_subscription_added event for a site subscription.
*/
public function test_calendar_subscription_created() {
public function test_calendar_subscription_created_site() {
global $CFG;
require_once($CFG->dirroot . '/calendar/lib.php');
$this->resetAfterTest(true);
......@@ -447,15 +447,115 @@ class core_calendar_events_testcase extends advanced_testcase {
$this->assertEquals($id, $event->objectid);
$this->assertEquals($subscription->courseid, $event->other['courseid']);
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
$this->assertArrayNotHasKey('categoryid', $event->other);
$this->assertArrayNotHasKey('groupid', $event->other);
$this->assertDebuggingNotCalled();
$sink->close();
}
/**
* Tests for calendar_subscription_added event for a category subscription.
*/
public function test_calendar_subscription_created_category() {
global $CFG;
require_once($CFG->dirroot . '/calendar/lib.php');
$this->resetAfterTest(true);
$categoryid = $this->course->category;
// Create a mock subscription.
$subscription = new stdClass();
$subscription->eventtype = 'category';
$subscription->name = 'test';
$subscription->categoryid = $categoryid;
// Trigger and capture the event.
$sink = $this->redirectEvents();
$id = calendar_add_subscription($subscription);
$events = $sink->get_events();
$event = reset($events);
// Check that the event data is valid.
$this->assertInstanceOf('\core\event\calendar_subscription_created', $event);
$this->assertEquals($id, $event->objectid);
$this->assertEquals($categoryid, $event->other['categoryid']);
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
$this->assertArrayNotHasKey('courseid', $event->other);
$this->assertArrayNotHasKey('groupid', $event->other);
$this->assertDebuggingNotCalled();
$sink->close();
}
/**
* Tests for calendar_subscription_added event for a course subscription.
*/
public function test_calendar_subscription_created_course() {
global $CFG;
require_once($CFG->dirroot . '/calendar/lib.php');
$this->resetAfterTest(true);
// Create a mock subscription.
$subscription = new stdClass();
$subscription->eventtype = 'course';
$subscription->name = 'test';
$subscription->courseid = $this->course->id;
// Trigger and capture the event.
$sink = $this->redirectEvents();
$id = calendar_add_subscription($subscription);
$events = $sink->get_events();
$event = reset($events);
// Check that the event data is valid.
$this->assertInstanceOf('\core\event\calendar_subscription_created', $event);
$this->assertEquals($id, $event->objectid);
$this->assertEquals($subscription->courseid, $event->other['courseid']);
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
$this->assertArrayNotHasKey('categoryid', $event->other);
$this->assertArrayNotHasKey('groupid', $event->other);
$this->assertDebuggingNotCalled();
$sink->close();
}
/**
* Tests for calendar_subscription_added event for a group subscription.
*/
public function test_calendar_subscription_created_group() {
global $CFG;
require_once($CFG->dirroot . '/calendar/lib.php');
$this->resetAfterTest(true);
$courseid = $this->course->id;
$groupid = 42;
// Create a mock subscription.
$subscription = new stdClass();
$subscription->eventtype = 'group';
$subscription->name = 'test';
$subscription->groupid = "{$courseid}-{$groupid}";
// Trigger and capture the event.
$sink = $this->redirectEvents();
$id = calendar_add_subscription($subscription);
$events = $sink->get_events();
$event = reset($events);
// Check that the event data is valid.
$this->assertInstanceOf('\core\event\calendar_subscription_created', $event);
$this->assertEquals($id, $event->objectid);
$this->assertEquals($courseid, $event->other['courseid']);
$this->assertEquals($groupid, $event->other['groupid']);
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
$this->assertArrayNotHasKey('categoryid', $event->other);
$this->assertDebuggingNotCalled();
$sink->close();
}
/**
* Tests for calendar_subscription_updated event.
* Tests for calendar_subscription_updated event for a site subscription.
*/
public function test_calendar_subscription_updated() {
public function test_calendar_subscription_updated_site() {
global $CFG;
require_once($CFG->dirroot . '/calendar/lib.php');
$this->resetAfterTest(true);
......@@ -479,15 +579,120 @@ class core_calendar_events_testcase extends advanced_testcase {
$this->assertEquals($subscription->id, $event->objectid);
$this->assertEquals($subscription->courseid, $event->other['courseid']);
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
$this->assertArrayNotHasKey('categoryid', $event->other);
$this->assertArrayNotHasKey('groupid', $event->other);
$this->assertDebuggingNotCalled();
$sink->close();
}
/**
* Tests for calendar_subscription_updated event for a category subscription.
*/
public function test_calendar_subscription_updated_category() {
global $CFG;
require_once($CFG->dirroot . '/calendar/lib.php');
$this->resetAfterTest(true);
$categoryid = $this->course->category;
// Create a mock subscription.
$subscription = new stdClass();
$subscription->eventtype = 'category';
$subscription->name = 'test';
$subscription->categoryid = $categoryid;
$subscription->id = calendar_add_subscription($subscription);
// Now edit it.
$subscription->name = 'awesome';
// Trigger and capture the event.
$sink = $this->redirectEvents();
calendar_update_subscription($subscription);
$events = $sink->get_events();
$event = reset($events);
// Check that the event data is valid.
$this->assertInstanceOf('\core\event\calendar_subscription_updated', $event);
$this->assertEquals($subscription->id, $event->objectid);
$this->assertEquals($categoryid, $event->other['categoryid']);
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
$this->assertArrayNotHasKey('courseid', $event->other);
$this->assertArrayNotHasKey('groupid', $event->other);
$this->assertDebuggingNotCalled();
$sink->close();
}