Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
<?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/>.

namespace core_calendar;

use core_calendar_external;
use externallib_advanced_testcase;

defined('MOODLE_INTERNAL') || die();

global $CFG;

require_once($CFG->dirroot . '/webservice/tests/helpers.php');

/**
 * External course functions unit tests
 *
 * @package    core_calendar
 * @category   external
 * @copyright  2012 Ankit Agarwal
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 * @since Moodle 2.5
 */
class externallib_test extends externallib_advanced_testcase {

    /**
     * Tests set up
     */
    protected function setUp(): void {
        global $CFG;
        require_once($CFG->dirroot . '/calendar/externallib.php');
    }

    /** Create calendar events or update them
     * Set $prop->id, if you want to do an update instead of creating an new event
     *
     * @param string $name        Event title
     * @param int    $userid      User id
     * @param string $type        Event type
     * @param int    $repeats     Number of repeated events to create
     * @param int    $timestart   Time stamp of the event start
     * @param mixed  $prop        List of event properties as array or object
     * @return mixed              Event object or false;
     * @since Moodle 2.5
     */

    public static function create_calendar_event($name, $userid = 0, $type = 'user', $repeats = 0, $timestart  = null, $prop = null) {
        global $CFG, $DB, $SITE;

        require_once("$CFG->dirroot/calendar/lib.php");
        if (!empty($prop)) {
            if (is_array($prop)) {
                $prop = (object)$prop;
            }
        } else {
            $prop = new \stdClass();
        }
        $prop->name = $name;
        if (empty($prop->eventtype)) {
            $prop->eventtype = $type;
        }
        if (empty($prop->repeats)) {
            $prop->repeats = $repeats;
        }
        if (empty($prop->timestart)) {
            $prop->timestart = time();
        }
        if (empty($prop->timeduration)) {
            $prop->timeduration = 0;
        }
        if (empty($prop->timesort)) {
            $prop->timesort = 0;
        }
        if (empty($prop->type)) {
            $prop->type = CALENDAR_EVENT_TYPE_STANDARD;
        }
        if (empty($prop->repeats)) {
            $prop->repeat = 0;
        } else {
            $prop->repeat = 1;
        }
        if (empty($prop->userid)) {
            if (!empty($userid)) {
                $prop->userid = $userid;
            } else {
                $prop->userid = 0;
            }
        }
        if (!isset($prop->courseid)) {
            // Set a default value of the event's course ID field.
            if ($type === 'user') {
                // If it's a user event, course ID should be zero.
                $prop->courseid = 0;
            } else {
                // Otherwise, default to the site ID.
                $prop->courseid = $SITE->id;
            }
        }

        // Determine event priority.
        if ($prop->courseid == 0 && isset($prop->groupid) && $prop->groupid == 0 && !empty($prop->userid)) {
            // User override event.
            $prop->priority = CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
        } else if ($prop->courseid != $SITE->id && !empty($prop->groupid)) {
            // Group override event.
            $priorityparams = ['courseid' => $prop->courseid, 'groupid' => $prop->groupid];
            // Group override event with the highest priority.
            $groupevents = $DB->get_records('event', $priorityparams, 'priority DESC', 'id, priority', 0, 1);
            $priority = 1;
            if (!empty($groupevents)) {
                $event = reset($groupevents);
                if (!empty($event->priority)) {
                    $priority = $event->priority + 1;
                }
            }
            $prop->priority = $priority;
        }

        $event = new \calendar_event($prop);
        return $event->create($prop);
    }

    public function test_create_calendar_events () {
        global $DB, $USER;

        $this->setAdminUser();
        $this->resetAfterTest();
        $prevcount = count($DB->get_records("event"));

        // Create a few events and do asserts.
        $this->create_calendar_event('test', $USER->id);
        $where = $DB->sql_compare_text('name') ." = ?";
        $count = count($DB->get_records_select("event", $where, array('test')));
        $this->assertEquals(1, $count);
        $aftercount = count($DB->get_records("event"));
        $this->assertEquals($prevcount + 1, $aftercount);

        $this->create_calendar_event('user', $USER->id, 'user', 3);
        $where = $DB->sql_compare_text('name') ." = ?";
        $count = count($DB->get_records_select("event", $where, array('user')));

        $this->assertEquals(3, $count);
        $aftercount = count($DB->get_records("event"));
        $this->assertEquals($prevcount + 4, $aftercount);

    }

    /**
     * Test delete_calendar_events
     */
    public function test_delete_calendar_events() {
        global $DB, $USER;

        $this->resetAfterTest(true);
        $this->setAdminUser();

        // Create a few stuff to test with.
        $user = $this->getDataGenerator()->create_user();
        $course = $this->getDataGenerator()->create_course();
        $record = new \stdClass();
        $record->courseid = $course->id;
        $group = $this->getDataGenerator()->create_group($record);

        $notdeletedcount = $DB->count_records('event');

        // Let's create a few events.
        $siteevent = $this->create_calendar_event('site', $USER->id, 'site');
        $record = new \stdClass();
        $record->courseid = $course->id;
        $courseevent = $this->create_calendar_event('course', $USER->id, 'course', 2, time(), $record);
        $userevent = $this->create_calendar_event('user', $USER->id);
        $record = new \stdClass();
        $record->courseid = $course->id;
        $record->groupid = $group->id;
        $groupevent = $this->create_calendar_event('group', $USER->id, 'group', 0, time(), $record);

        // Now lets try to delete stuff with proper rights.
        $events = array(
                array('eventid' => $siteevent->id, 'repeat' => 0),
                array('eventid' => $courseevent->id, 'repeat' => 1),
                array('eventid' => $userevent->id, 'repeat' => 0),
                array('eventid' => $groupevent->id, 'repeat' => 0)
                );
        core_calendar_external::delete_calendar_events($events);

        // Check to see if things were deleted properly.
        $deletedcount = $DB->count_records('event');
        $this->assertEquals($notdeletedcount, $deletedcount);

        // Let's create a few events.
        $siteevent = $this->create_calendar_event('site', $USER->id, 'site');
        $record = new \stdClass();
        $record->courseid = $course->id;
        $courseevent = $this->create_calendar_event('course', $USER->id, 'course', 3, time(), $record);
        $userevent = $this->create_calendar_event('user', $user->id);
        $record = new \stdClass();
        $record->courseid = $course->id;
        $record->groupid = $group->id;
        $groupevent = $this->create_calendar_event('group', $USER->id, 'group', 0, time(), $record);

        $this->setuser($user);
        $sitecontext = \context_system::instance();
        $coursecontext = \context_course::instance($course->id);
        $usercontext = \context_user::instance($user->id);
        $role = $DB->get_record('role', array('shortname' => 'student'));
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);

        // Remove all caps.
        $this->unassignUserCapability('moodle/calendar:manageentries', $sitecontext->id, $role->id);
        $this->unassignUserCapability('moodle/calendar:manageentries', $coursecontext->id, $role->id);
        $this->unassignUserCapability('moodle/calendar:managegroupentries', $coursecontext->id, $role->id);
        $this->unassignUserCapability('moodle/calendar:manageownentries', $usercontext->id, $role->id);

        // Assign proper caps and attempt delete.
         $this->assignUserCapability('moodle/calendar:manageentries', $sitecontext->id, $role->id);
         $events = array(
                array('eventid' => $siteevent->id, 'repeat' => 0),
                );
        core_calendar_external::delete_calendar_events($events);
        $deletedcount = $DB->count_records('event');
        $count = $notdeletedcount+5;
        $this->assertEquals($count, $deletedcount);

         $this->assignUserCapability('moodle/calendar:manageentries', $sitecontext->id, $role->id);
         $events = array(
                array('eventid' => $courseevent->id, 'repeat' => 0),
                );
        core_calendar_external::delete_calendar_events($events);
        $deletedcount = $DB->count_records('event');
        $count = $notdeletedcount+4;
        $this->assertEquals($count, $deletedcount);

         $this->assignUserCapability('moodle/calendar:manageownentries', $usercontext->id, $role->id);
         $events = array(
                array('eventid' => $userevent->id, 'repeat' => 0),
                );
        core_calendar_external::delete_calendar_events($events);
        $deletedcount = $DB->count_records('event');
        $count = $notdeletedcount+3;
        $this->assertEquals($count, $deletedcount);

         $this->assignUserCapability('moodle/calendar:managegroupentries', $coursecontext->id, $role->id);
         $events = array(
                array('eventid' => $groupevent->id, 'repeat' => 0),
                );
        core_calendar_external::delete_calendar_events($events);
        $deletedcount = $DB->count_records('event');
        $count = $notdeletedcount+2;
        $this->assertEquals($count, $deletedcount);

        $notdeletedcount = $deletedcount;

        // Let us try deleting without caps.

        $siteevent = $this->create_calendar_event('site', $USER->id, 'site');
        $record = new \stdClass();
        $record->courseid = $course->id;
        $courseevent = $this->create_calendar_event('course', $USER->id, 'course', 3, time(), $record);
        $userevent = $this->create_calendar_event('user', $USER->id);
        $record = new \stdClass();
        $record->courseid = $course->id;
        $record->groupid = $group->id;
        $groupevent = $this->create_calendar_event('group', $USER->id, 'group', 0, time(), $record);

        $this->setGuestUser();

        $events = array(
            array('eventid' => $siteevent->id, 'repeat' => 0),
            array('eventid' => $courseevent->id, 'repeat' => 0),
            array('eventid' => $userevent->id, 'repeat' => 0),
            array('eventid' => $groupevent->id, 'repeat' => 0)
        );
        $this->expectException(\moodle_exception::class);
        core_calendar_external::delete_calendar_events($events);
    }

    /**
     * Test get_calendar_events
     */
    public function test_get_calendar_events() {
        global $DB, $USER;

        $this->resetAfterTest(true);
        set_config('calendar_adminseesall', 1);
        $this->setAdminUser();

        // Create a few stuff to test with.
        $user = $this->getDataGenerator()->create_user();
        $user2 = $this->getDataGenerator()->create_user();
        $course = $this->getDataGenerator()->create_course();

        $category = $this->getDataGenerator()->create_category();

        $category2 = $this->getDataGenerator()->create_category();
        $category2b = $this->getDataGenerator()->create_category(['parent' => $category2->id]);
        $course3 = $this->getDataGenerator()->create_course(['category' => $category2b->id]);

        $role = $DB->get_record('role', array('shortname' => 'student'));
        $this->getDataGenerator()->enrol_user($user2->id, $course3->id, $role->id);

        $record = new \stdClass();
        $record->courseid = $course->id;
        $group = $this->getDataGenerator()->create_group($record);

        $beforecount = $DB->count_records('event');

        // Let's create a few events.
        $siteevent = $this->create_calendar_event('site', $USER->id, 'site');

        // This event will have description with an inline fake image.
        $draftidfile = file_get_unused_draft_itemid();
        $usercontext = \context_course::instance($course->id);
        $filerecord = array(
            'contextid' => $usercontext->id,
            'component' => 'user',
            'filearea'  => 'draft',
            'itemid'    => $draftidfile,
            'filepath'  => '/',
            'filename'  => 'fakeimage.png',
        );
        $fs = get_file_storage();
        $fs->create_file_from_string($filerecord, 'img contents');

        $record = new \stdClass();
        $record->courseid = $course->id;
        $record->groupid = 0;
        $record->description = array(
            'format' => FORMAT_HTML,
            'text' => 'Text with img <img src="@@PLUGINFILE@@/fakeimage.png">',
            'itemid' => $draftidfile
        );
        $courseevent = $this->create_calendar_event('course', $USER->id, 'course', 2, time(), $record);

        $record = new \stdClass();
        $record->courseid = 0;
        $record->groupid = 0;
        $userevent = $this->create_calendar_event('user', $USER->id, 'user', 0, time(), $record);

        $record = new \stdClass();
        $record->courseid = $course->id;
        $record->groupid = $group->id;
        $groupevent = $this->create_calendar_event('group', $USER->id, 'group', 0, time(), $record);

        $paramevents = array ('eventids' => array($siteevent->id), 'courseids' => array($course->id),
                'groupids' => array($group->id), 'categoryids' => array($category->id));

        $options = array ('siteevents' => true, 'userevents' => true);
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);

        // Check to see if we got all events.
        $this->assertEquals(5, count($events['events']));
        $this->assertEquals(0, count($events['warnings']));
        $options = array ('siteevents' => true, 'userevents' => true, 'timeend' => time() + 7*WEEKSECS);
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(5, count($events['events']));
        $this->assertEquals(0, count($events['warnings']));

        // Expect the same URL in the description of two different events (because they are repeated).
        $coursecontext = \context_course::instance($course->id);
        $expectedurl = "webservice/pluginfile.php/$coursecontext->id/calendar/event_description/$courseevent->id/fakeimage.png";
        $withdescription = 0;
        foreach ($events['events'] as $event) {
            if (!empty($event['description'])) {
                $withdescription++;
                $this->assertStringContainsString($expectedurl, $event['description']);
            }
        }
        $this->assertEquals(2, $withdescription);

        // Let's play around with caps.

        // Create user event for the user $user.
        $record = new \stdClass();
        $record->courseid = 0;
        $record->groupid = 0;
        $this->create_calendar_event('user', $user->id, 'user', 0, time(), $record);

        $this->setUser($user);
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(2, count($events['events'])); // site, user.
        $this->assertEquals(2, count($events['warnings'])); // course, group.

        $role = $DB->get_record('role', array('shortname' => 'student'));
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(4, count($events['events'])); // site, user, both course events.
        $this->assertEquals(1, count($events['warnings'])); // group.

        $options = array ('siteevents' => true, 'userevents' => true, 'timeend' => time() + HOURSECS);
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(3, count($events['events'])); // site, user, one course event.
        $this->assertEquals(1, count($events['warnings'])); // group.

        groups_add_member($group, $user);
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(4, count($events['events'])); // site, user, group, one course event.
        $this->assertEquals(0, count($events['warnings']));

        $paramevents = array ('courseids' => array($course->id), 'groupids' => array($group->id));
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(4, count($events['events'])); // site, user, group, one course event.
        $this->assertEquals(0, count($events['warnings']));

        $paramevents = array ('groupids' => array($group->id, 23));
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(3, count($events['events'])); // site, user, group.
        $this->assertEquals(1, count($events['warnings']));

        $paramevents = array ('courseids' => array(23));
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(2, count($events['events'])); // site, user.
        $this->assertEquals(1, count($events['warnings']));

        $paramevents = array ();
        $options = array ('siteevents' => false, 'userevents' => false, 'timeend' => time() + 7*WEEKSECS);
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(0, count($events['events'])); // nothing returned.
        $this->assertEquals(0, count($events['warnings']));

        $paramevents = array ('eventids' => array($siteevent->id, $groupevent->id));
        $options = array ('siteevents' => false, 'userevents' => false, 'timeend' => time() + 7*WEEKSECS);
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(2, count($events['events'])); // site, group.
        $this->assertEquals(0, count($events['warnings']));

        $paramevents = array ('eventids' => array($siteevent->id));
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(1, count($events['events'])); // site.
        $this->assertEquals(0, count($events['warnings']));

        // Try getting a course event by its id.
        $paramevents = array ('eventids' => array($courseevent->id));
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(1, count($events['events']));
        $this->assertEquals(0, count($events['warnings']));

        // Now, create an activity event.
        $this->setAdminUser();
        $nexttime = time() + DAYSECS;
        $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id, 'duedate' => $nexttime));

        $this->setUser($user);
        $paramevents = array ('courseids' => array($course->id));
        $options = array ('siteevents' => true, 'userevents' => true, 'timeend' => time() + WEEKSECS);
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);

        $this->assertCount(5, $events['events']);

        // Hide the assignment.
        set_coursemodule_visible($assign->cmid, 0);
        // Empty all the caches that may be affected  by this change.
        accesslib_clear_all_caches_for_unit_testing();
        \course_modinfo::clear_instance_cache();

        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        // Expect one less.
        $this->assertCount(4, $events['events']);

        // Create some category events.
        $this->setAdminUser();
        $record = new \stdClass();
        $record->courseid = 0;
        $record->categoryid = $category->id;
        $record->timestart = time() - DAYSECS;
        $catevent1 = $this->create_calendar_event('category a', $USER->id, 'category', 0, time(), $record);

        $record = new \stdClass();
        $record->courseid = 0;
        $record->categoryid = $category2->id;
        $record->timestart = time() + DAYSECS;
        $catevent2 = $this->create_calendar_event('category b', $USER->id, 'category', 0, time(), $record);

        // Now as student, make sure we get the events of the courses I am enrolled.
        $this->setUser($user2);
        $paramevents = array('categoryids' => array($category2b->id));
        $options = array('timeend' => time() + 7 * WEEKSECS, 'userevents' => false, 'siteevents' => false);
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);

        // Should be just one, since there's just one category event of the course I am enrolled (course3 - cat2b).
        $this->assertEquals(1, count($events['events']));
        $this->assertEquals($catevent2->id, $events['events'][0]['id']);
        $this->assertEquals($category2->id, $events['events'][0]['categoryid']);
        $this->assertEquals(0, count($events['warnings']));

        // Now get category events but by course (there aren't course events in the course).
        $paramevents = array('courseids' => array($course3->id));
        $options = array('timeend' => time() + 7 * WEEKSECS, 'userevents' => false, 'siteevents' => false);
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(1, count($events['events']));
        $this->assertEquals($catevent2->id, $events['events'][0]['id']);
        $this->assertEquals(0, count($events['warnings']));

        // Empty events in one where I'm not enrolled and one parent category
        // (parent of a category where this is a course where the user is enrolled).
        $paramevents = array('categoryids' => array($category2->id, $category->id));
        $options = array('timeend' => time() + 7 * WEEKSECS, 'userevents' => false, 'siteevents' => false);
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(1, count($events['events']));
        $this->assertEquals($catevent2->id, $events['events'][0]['id']);
        $this->assertEquals(0, count($events['warnings']));

        // Admin can see all category events.
        $this->setAdminUser();
        $paramevents = array('categoryids' => array($category->id, $category2->id, $category2b->id));
        $options = array('timeend' => time() + 7 * WEEKSECS, 'userevents' => false, 'siteevents' => false);
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(2, count($events['events']));
        $this->assertEquals(0, count($events['warnings']));
        $this->assertEquals($catevent1->id, $events['events'][0]['id']);
        $this->assertEquals($category->id, $events['events'][0]['categoryid']);
        $this->assertEquals($catevent2->id, $events['events'][1]['id']);
        $this->assertEquals($category2->id, $events['events'][1]['categoryid']);
    }

    /**
     * Test get_calendar_events with mathjax in the name.
     */
    public function test_get_calendar_events_with_mathjax() {
        global $USER;

        $this->resetAfterTest(true);
        set_config('calendar_adminseesall', 1);
        $this->setAdminUser();

        // Enable MathJax filter in content and headings.
        $this->configure_filters([
            ['name' => 'mathjaxloader', 'state' => TEXTFILTER_ON, 'move' => -1, 'applytostrings' => true],
        ]);

        // Create a site event with mathjax in the name and description.
        $siteevent = $this->create_calendar_event('Site Event $$(a+b)=2$$', $USER->id, 'site', 0, time(),
                ['description' => 'Site Event Description $$(a+b)=2$$']);

        // Now call the WebService.
        $events = core_calendar_external::get_calendar_events();
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);

        // Format the original data.
        $sitecontext = \context_system::instance();
        $siteevent->name = $siteevent->format_external_name();
        list($siteevent->description, $siteevent->descriptionformat) = $siteevent->format_external_text();

        // Check that the event data is formatted.
        $this->assertCount(1, $events['events']);
        $this->assertStringContainsString('<span class="filter_mathjaxloader_equation">', $events['events'][0]['name']);
        $this->assertStringContainsString('<span class="filter_mathjaxloader_equation">', $events['events'][0]['description']);
        $this->assertEquals($siteevent->name, $events['events'][0]['name']);
        $this->assertEquals($siteevent->description, $events['events'][0]['description']);
    }

    /**
     * Test core_calendar_external::create_calendar_events
     */
    public function test_core_create_calendar_events() {
        global $DB, $USER, $SITE;

        $this->resetAfterTest(true);
        $this->setAdminUser();

        // Create a few stuff to test with.
        $user = $this->getDataGenerator()->create_user();
        $course = $this->getDataGenerator()->create_course();
        $record = new \stdClass();
        $record->courseid = $course->id;
        $group = $this->getDataGenerator()->create_group($record);

        $prevcount = $DB->count_records('event');

        // Let's create a few events.
        $events = array (
                array('name' => 'site', 'courseid' => $SITE->id, 'eventtype' => 'site'),
                array('name' => 'course', 'courseid' => $course->id, 'eventtype' => 'course', 'repeats' => 2),
                array('name' => 'group', 'courseid' => $course->id, 'groupid' => $group->id, 'eventtype' => 'group'),
                array('name' => 'user')
                );
        $eventsret = core_calendar_external::create_calendar_events($events);
        $eventsret = \external_api::clean_returnvalue(core_calendar_external::create_calendar_events_returns(), $eventsret);

        // Check to see if things were created properly.
        $aftercount = $DB->count_records('event');
        $this->assertEquals($prevcount + 5, $aftercount);
        $this->assertEquals(5, count($eventsret['events']));
        $this->assertEquals(0, count($eventsret['warnings']));

        $sitecontext = \context_system::instance();
        $coursecontext = \context_course::instance($course->id);

        $this->setUser($user);
        $prevcount = $aftercount;
        $events = array (
                array('name' => 'course', 'courseid' => $course->id, 'eventtype' => 'course', 'repeats' => 2),
                array('name' => 'group', 'courseid' => $course->id, 'groupid' => $group->id, 'eventtype' => 'group'),
                array('name' => 'user')
        );
        $role = $DB->get_record('role', array('shortname' => 'student'));
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
        groups_add_member($group, $user);
        $this->assignUserCapability('moodle/calendar:manageentries', $coursecontext->id, $role->id);
        $this->assignUserCapability('moodle/calendar:managegroupentries', $coursecontext->id, $role->id);
        $eventsret = core_calendar_external::create_calendar_events($events);
        $eventsret = \external_api::clean_returnvalue(core_calendar_external::create_calendar_events_returns(), $eventsret);
        // Check to see if things were created properly.
        $aftercount = $DB->count_records('event');
        $this->assertEquals($prevcount + 4, $aftercount);
        $this->assertEquals(4, count($eventsret['events']));
        $this->assertEquals(0, count($eventsret['warnings']));

        // Check to see nothing was created without proper permission.
        $this->setGuestUser();
        $prevcount = $DB->count_records('event');
        $eventsret = core_calendar_external::create_calendar_events($events);
        $eventsret = \external_api::clean_returnvalue(core_calendar_external::create_calendar_events_returns(), $eventsret);
        $aftercount = $DB->count_records('event');
        $this->assertEquals($prevcount, $aftercount);
        $this->assertEquals(0, count($eventsret['events']));
        $this->assertEquals(3, count($eventsret['warnings']));

        $this->setUser($user);
        $this->unassignUserCapability('moodle/calendar:manageentries', $coursecontext->id, $role->id);
        $this->unassignUserCapability('moodle/calendar:managegroupentries', $coursecontext->id, $role->id);
        $prevcount = $DB->count_records('event');
        $eventsret = core_calendar_external::create_calendar_events($events);
        $eventsret = \external_api::clean_returnvalue(core_calendar_external::create_calendar_events_returns(), $eventsret);
        $aftercount = $DB->count_records('event');
        $this->assertEquals($prevcount + 1, $aftercount); // User event.
        $this->assertEquals(1, count($eventsret['events']));
        $this->assertEquals(2, count($eventsret['warnings']));
    }

    /**
     * Requesting calendar events from a given time should return all events with a sort
     * time at or after the requested time. All events prior to that time should not
     * be return.
     *
     * If there are no events on or after the given time then an empty result set should
     * be returned.
     */
    public function test_get_calendar_action_events_by_timesort_after_time() {
        $user = $this->getDataGenerator()->create_user();
        $course = $this->getDataGenerator()->create_course();
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
        $moduleinstance = $generator->create_instance(['course' => $course->id]);

        $this->getDataGenerator()->enrol_user($user->id, $course->id);
        $this->resetAfterTest(true);
        $this->setUser($user);

        $params = [
            'type' => CALENDAR_EVENT_TYPE_ACTION,
            'modulename' => 'assign',
            'instance' => $moduleinstance->id,
            'courseid' => $course->id,
        ];

        $event1 = $this->create_calendar_event('Event 1', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 1]));
        $event2 = $this->create_calendar_event('Event 2', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 2]));
        $event3 = $this->create_calendar_event('Event 3', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 3]));
        $event4 = $this->create_calendar_event('Event 4', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 4]));
        $event5 = $this->create_calendar_event('Event 5', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 5]));
        $event6 = $this->create_calendar_event('Event 6', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 6]));
        $event7 = $this->create_calendar_event('Event 7', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 7]));
        $event8 = $this->create_calendar_event('Event 8', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 8]));

        $result = core_calendar_external::get_calendar_action_events_by_timesort(5);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_timesort_returns(),
            $result
        );
        $events = $result['events'];

        $this->assertCount(4, $events);
        $this->assertEquals('Event 5', $events[0]['name']);
        $this->assertEquals('Event 6', $events[1]['name']);
        $this->assertEquals('Event 7', $events[2]['name']);
        $this->assertEquals('Event 8', $events[3]['name']);
        $this->assertEquals($event5->id, $result['firstid']);
        $this->assertEquals($event8->id, $result['lastid']);

        $result = core_calendar_external::get_calendar_action_events_by_timesort(9);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_timesort_returns(),
            $result
        );

        $this->assertEmpty($result['events']);
        $this->assertNull($result['firstid']);
        $this->assertNull($result['lastid']);

        // Requesting action events on behalf of another user.
        $this->setAdminUser();
        $result = core_calendar_external::get_calendar_action_events_by_timesort(5, null, 0, 20, false, $user->id);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_timesort_returns(),
            $result
        );
        $events = $result['events'];

        $this->assertCount(4, $events);
        $this->assertEquals('Event 5', $events[0]['name']);
        $this->assertEquals('Event 6', $events[1]['name']);
        $this->assertEquals('Event 7', $events[2]['name']);
        $this->assertEquals('Event 8', $events[3]['name']);
        $this->assertEquals($event5->id, $result['firstid']);
        $this->assertEquals($event8->id, $result['lastid']);
    }

    /**
     * Requesting calendar events before a given time should return all events with a sort
     * time at or before the requested time (inclusive). All events after that time
     * should not be returned.
     *
     * If there are no events before the given time then an empty result set should be
     * returned.
     */
    public function test_get_calendar_action_events_by_timesort_before_time() {
        $user = $this->getDataGenerator()->create_user();
        $course = $this->getDataGenerator()->create_course();
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
        $moduleinstance = $generator->create_instance(['course' => $course->id]);

        $this->getDataGenerator()->enrol_user($user->id, $course->id);
        $this->resetAfterTest(true);
        $this->setUser($user);

        $params = [
            'type' => CALENDAR_EVENT_TYPE_ACTION,
            'modulename' => 'assign',
            'instance' => $moduleinstance->id,
            'courseid' => $course->id,
        ];

        $event1 = $this->create_calendar_event('Event 1', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 2]));
        $event2 = $this->create_calendar_event('Event 2', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 3]));
        $event3 = $this->create_calendar_event('Event 3', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 4]));
        $event4 = $this->create_calendar_event('Event 4', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 5]));
        $event5 = $this->create_calendar_event('Event 5', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 6]));
        $event6 = $this->create_calendar_event('Event 6', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 7]));
        $event7 = $this->create_calendar_event('Event 7', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 8]));
        $event8 = $this->create_calendar_event('Event 8', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 9]));

        $result = core_calendar_external::get_calendar_action_events_by_timesort(null, 5);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_timesort_returns(),
            $result
        );
        $events = $result['events'];

        $this->assertCount(4, $events);
        $this->assertEquals('Event 1', $events[0]['name']);
        $this->assertEquals('Event 2', $events[1]['name']);
        $this->assertEquals('Event 3', $events[2]['name']);
        $this->assertEquals('Event 4', $events[3]['name']);
        $this->assertEquals($event1->id, $result['firstid']);
        $this->assertEquals($event4->id, $result['lastid']);

        $result = core_calendar_external::get_calendar_action_events_by_timesort(null, 1);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_timesort_returns(),
            $result
        );

        $this->assertEmpty($result['events']);
        $this->assertNull($result['firstid']);
        $this->assertNull($result['lastid']);

        // Requesting action events on behalf of another user.
        $this->setAdminUser();

        $result = core_calendar_external::get_calendar_action_events_by_timesort(null, 5, 0, 20, false, $user->id);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_timesort_returns(),
            $result
        );
        $events = $result['events'];

        $this->assertCount(4, $events);
        $this->assertEquals('Event 1', $events[0]['name']);
        $this->assertEquals('Event 2', $events[1]['name']);
        $this->assertEquals('Event 3', $events[2]['name']);
        $this->assertEquals('Event 4', $events[3]['name']);
        $this->assertEquals($event1->id, $result['firstid']);
        $this->assertEquals($event4->id, $result['lastid']);
    }

    /**
     * Test retrieving event that was overridden for a user
     */
    public function test_get_calendar_events_override() {
        $user = $this->getDataGenerator()->create_user();
        $user2 = $this->getDataGenerator()->create_user();
        $teacher = $this->getDataGenerator()->create_user();
        $anotheruser = $this->getDataGenerator()->create_user();
        $course = $this->getDataGenerator()->create_course();
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
        $moduleinstance = $generator->create_instance(['course' => $course->id]);

        $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
        $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
        $this->resetAfterTest(true);
        $this->setAdminUser();

        $params = [
            'type' => CALENDAR_EVENT_TYPE_ACTION,
            'modulename' => 'assign',
            'instance' => $moduleinstance->id,
        ];

        $now = time();
        // Create two events - one for everybody in the course and one only for the first student.
        $event1 = $this->create_calendar_event('Base event', 0, 'due', 0, $now + DAYSECS, $params + ['courseid' => $course->id]);
        $event2 = $this->create_calendar_event('User event', $user->id, 'due', 0, $now + 2*DAYSECS, $params + ['courseid' => 0]);

        // Retrieve course events for the second student - only one "Base event" is returned.
        $this->setUser($user2);
        $paramevents = array('courseids' => array($course->id));
        $options = array ('siteevents' => true, 'userevents' => true);
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(1, count($events['events']));
        $this->assertEquals(0, count($events['warnings']));
        $this->assertEquals('Base event', $events['events'][0]['name']);

        // Retrieve events for the first student - both events are returned.
        $this->setUser($user);
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(2, count($events['events']));
        $this->assertEquals(0, count($events['warnings']));
        $this->assertEquals('Base event', $events['events'][0]['name']);
        $this->assertEquals('User event', $events['events'][1]['name']);

        // Retrieve events by id as a teacher, 'User event' should be returned since teacher has access to this course.
        $this->setUser($teacher);
        $paramevents = ['eventids' => [$event2->id]];
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(1, count($events['events']));
        $this->assertEquals(0, count($events['warnings']));
        $this->assertEquals('User event', $events['events'][0]['name']);

        // Retrieve events by id as another user, nothing should be returned.
        $this->setUser($anotheruser);
        $paramevents = ['eventids' => [$event2->id, $event1->id]];
        $events = core_calendar_external::get_calendar_events($paramevents, $options);
        $events = \external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
        $this->assertEquals(0, count($events['events']));
        $this->assertEquals(0, count($events['warnings']));
    }

    /**
     * Requesting calendar events within a given time range should return all events with
     * a sort time between the lower and upper time bound (inclusive).
     *
     * If there are no events in the given time range then an empty result set should be
     * returned.
     */
    public function test_get_calendar_action_events_by_timesort_time_range() {
        $user = $this->getDataGenerator()->create_user();
        $course = $this->getDataGenerator()->create_course();
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
        $moduleinstance = $generator->create_instance(['course' => $course->id]);

        $this->getDataGenerator()->enrol_user($user->id, $course->id);
        $this->resetAfterTest(true);
        $this->setUser($user);

        $params = [
            'type' => CALENDAR_EVENT_TYPE_ACTION,
            'modulename' => 'assign',
            'instance' => $moduleinstance->id,
            'courseid' => $course->id,
        ];

        $event1 = $this->create_calendar_event('Event 1', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 1]));
        $event2 = $this->create_calendar_event('Event 2', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 2]));
        $event3 = $this->create_calendar_event('Event 3', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 3]));
        $event4 = $this->create_calendar_event('Event 4', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 4]));
        $event5 = $this->create_calendar_event('Event 5', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 5]));
        $event6 = $this->create_calendar_event('Event 6', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 6]));
        $event7 = $this->create_calendar_event('Event 7', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 7]));
        $event8 = $this->create_calendar_event('Event 8', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 8]));

        $result = core_calendar_external::get_calendar_action_events_by_timesort(3, 6);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_timesort_returns(),
            $result
        );
        $events = $result['events'];

        $this->assertCount(4, $events);
        $this->assertEquals('Event 3', $events[0]['name']);
        $this->assertEquals('Event 4', $events[1]['name']);
        $this->assertEquals('Event 5', $events[2]['name']);
        $this->assertEquals('Event 6', $events[3]['name']);
        $this->assertEquals($event3->id, $result['firstid']);
        $this->assertEquals($event6->id, $result['lastid']);

        $result = core_calendar_external::get_calendar_action_events_by_timesort(10, 15);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_timesort_returns(),
            $result
        );

        $this->assertEmpty($result['events']);
        $this->assertNull($result['firstid']);
        $this->assertNull($result['lastid']);
    }

    /**
     * Requesting calendar events within a given time range and a limit and offset should return
     * the number of events up to the given limit value that have a sort time between the lower
     * and uppper time bound (inclusive) where the result set is shifted by the offset value.
     *
     * If there are no events in the given time range then an empty result set should be
     * returned.
     */
    public function test_get_calendar_action_events_by_timesort_time_limit_offset() {
        $user = $this->getDataGenerator()->create_user();
        $course = $this->getDataGenerator()->create_course();
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
        $moduleinstance = $generator->create_instance(['course' => $course->id]);

        $this->getDataGenerator()->enrol_user($user->id, $course->id);
        $this->resetAfterTest(true);
        $this->setUser($user);

        $params = [
            'type' => CALENDAR_EVENT_TYPE_ACTION,
            'modulename' => 'assign',
            'instance' => $moduleinstance->id,
            'courseid' => $course->id,
        ];

        $event1 = $this->create_calendar_event('Event 1', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 1]));
        $event2 = $this->create_calendar_event('Event 2', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 2]));
        $event3 = $this->create_calendar_event('Event 3', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 3]));
        $event4 = $this->create_calendar_event('Event 4', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 4]));
        $event5 = $this->create_calendar_event('Event 5', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 5]));
        $event6 = $this->create_calendar_event('Event 6', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 6]));
        $event7 = $this->create_calendar_event('Event 7', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 7]));
        $event8 = $this->create_calendar_event('Event 8', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 8]));

        $result = core_calendar_external::get_calendar_action_events_by_timesort(2, 7, $event3->id, 2);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_timesort_returns(),
            $result
        );
        $events = $result['events'];

        $this->assertCount(2, $events);
        $this->assertEquals('Event 4', $events[0]['name']);
        $this->assertEquals('Event 5', $events[1]['name']);
        $this->assertEquals($event4->id, $result['firstid']);
        $this->assertEquals($event5->id, $result['lastid']);

        $result = core_calendar_external::get_calendar_action_events_by_timesort(2, 7, $event5->id, 2);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_timesort_returns(),
            $result
        );
        $events = $result['events'];

        $this->assertCount(2, $events);
        $this->assertEquals('Event 6', $events[0]['name']);
        $this->assertEquals('Event 7', $events[1]['name']);
        $this->assertEquals($event6->id, $result['firstid']);
        $this->assertEquals($event7->id, $result['lastid']);

        $result = core_calendar_external::get_calendar_action_events_by_timesort(2, 7, $event7->id, 2);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_timesort_returns(),
            $result
        );

        $this->assertEmpty($result['events']);
        $this->assertNull($result['firstid']);
        $this->assertNull($result['lastid']);
    }

    /**
     * Check that it is possible to restrict the calendar events to events where the user is not suspended in the course.
     */
    public function test_get_calendar_action_events_by_timesort_suspended_course() {
        $this->resetAfterTest();
        $user1 = $this->getDataGenerator()->create_user();
        $user2 = $this->getDataGenerator()->create_user();
        $course = $this->getDataGenerator()->create_course();
        $this->setAdminUser();
        $lesson = $this->getDataGenerator()->create_module('lesson', [
                'name' => 'Lesson 1',
                'course' => $course->id,
                'available' => time(),
                'deadline' => (time() + (60 * 60 * 24 * 5))
            ]
        );
        $this->getDataGenerator()->enrol_user($user1->id, $course->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
        $this->getDataGenerator()->enrol_user($user2->id, $course->id);

        $this->setUser($user1);
        $result = core_calendar_external::get_calendar_action_events_by_timesort(0, null, 0, 20, true);
        $this->assertEmpty($result->events);
        $this->setUser($user2);
        $result = core_calendar_external::get_calendar_action_events_by_timesort(0, null, 0, 20, true);
        $this->assertCount(1, $result->events);
        $this->assertEquals('Lesson 1 closes', $result->events[0]->name);
    }

    /**
     * Check that it is possible to get other user's events without the permission.
     */
    public function test_get_calendar_action_events_by_timesort_for_other_users() {
        $this->resetAfterTest();
        // Create test users.
        $user1 = $this->getDataGenerator()->create_user(['email' => 'student1@localhost.com']);
        $user2 = $this->getDataGenerator()->create_user(['email' => 'student2@localhost.com']);
        // Create test course.
        $course = $this->getDataGenerator()->create_course();
        $this->setAdminUser();
        // Create test activity and make it available only for student2.
        $lesson = $this->getDataGenerator()->create_module('lesson', [
                'name' => 'Lesson 1',
                'course' => $course->id,
                'available' => time(),
                'deadline' => (time() + (60 * 60 * 24 * 5)),
                'availability' => '{"op":"&","c":[{"type":"profile","sf":"email","op":"isequalto","v":"student2@localhost.com"}],"showc":[true]}'
            ]
        );
        // Enrol.
        $this->getDataGenerator()->enrol_user($user1->id, $course->id);
        $this->getDataGenerator()->enrol_user($user2->id, $course->id);

        // Student2 can see the event.
        $this->setUser($user2);
        $result = core_calendar_external::get_calendar_action_events_by_timesort(0, null, 0, 20, true);
        $this->assertCount(1, $result->events);
        $this->assertEquals('Lesson 1 closes', $result->events[0]->name);

        // Student1 cannot see the event.
        $this->setUser($user1);
        $result = core_calendar_external::get_calendar_action_events_by_timesort(0, null, 0, 20, true);
        $this->assertEmpty($result->events);

        // Admin, Manager, Teacher can view student2's data.
        $this->setAdminUser();
        $result = core_calendar_external::get_calendar_action_events_by_timesort(0, null, 0, 20, true, $user2->id);
        $this->assertCount(1, $result->events);
        $this->assertEquals('Lesson 1 closes', $result->events[0]->name);

        // Student1 will see an exception if he/she trying to view student2's data.
        $this->setUser($user1);
        $this->expectException(\required_capability_exception::class);
        $this->expectExceptionMessage('error/nopermission');
        $result = core_calendar_external::get_calendar_action_events_by_timesort(0, null, 0, 20, true, $user2->id);
    }

    /**
     * Requesting calendar events from a given course and time should return all
     * events with a sort time at or after the requested time. All events prior
     * to that time should not be return.
     *
     * If there are no events on or after the given time then an empty result set should
     * be returned.
     */
    public function test_get_calendar_action_events_by_course_after_time() {
        $user = $this->getDataGenerator()->create_user();
        $course1 = $this->getDataGenerator()->create_course();
        $course2 = $this->getDataGenerator()->create_course();
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
        $instance1 = $generator->create_instance(['course' => $course1->id]);
        $instance2 = $generator->create_instance(['course' => $course2->id]);
        $records = [];

        $this->getDataGenerator()->enrol_user($user->id, $course1->id);
        $this->getDataGenerator()->enrol_user($user->id, $course2->id);
        $this->resetAfterTest(true);
        $this->setUser($user);

        for ($i = 1; $i < 19; $i++) {
            $courseid = ($i < 9) ? $course1->id : $course2->id;
            $instance = ($i < 9) ? $instance1->id : $instance2->id;
            $records[] = $this->create_calendar_event(
                sprintf('Event %d', $i),
                $user->id,
                'user',
                0,
                1,
                [
                    'type' => CALENDAR_EVENT_TYPE_ACTION,
                    'courseid' => $courseid,
                    'timesort' => $i,
                    'modulename' => 'assign',
                    'instance' => $instance,
                ]
            );
        }

        $result = core_calendar_external::get_calendar_action_events_by_course($course1->id, 5);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_course_returns(),
            $result
        );
        $result = $result['events'];

        $this->assertCount(4, $result);
        $this->assertEquals('Event 5', $result[0]['name']);
        $this->assertEquals('Event 6', $result[1]['name']);
        $this->assertEquals('Event 7', $result[2]['name']);
        $this->assertEquals('Event 8', $result[3]['name']);

        $result = core_calendar_external::get_calendar_action_events_by_course($course1->id, 9);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_course_returns(),
            $result
        );
        $result = $result['events'];

        $this->assertEmpty($result);
    }

    /**
     * Requesting calendar events for a course and before a given time should return
     * all events with a sort time at or before the requested time (inclusive). All
     * events after that time should not be returned.
     *
     * If there are no events before the given time then an empty result set should be
     * returned.
     */
    public function test_get_calendar_action_events_by_course_before_time() {
        $user = $this->getDataGenerator()->create_user();
        $course1 = $this->getDataGenerator()->create_course();
        $course2 = $this->getDataGenerator()->create_course();
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
        $instance1 = $generator->create_instance(['course' => $course1->id]);
        $instance2 = $generator->create_instance(['course' => $course2->id]);
        $records = [];

        $this->getDataGenerator()->enrol_user($user->id, $course1->id);
        $this->getDataGenerator()->enrol_user($user->id, $course2->id);
        $this->resetAfterTest(true);
        $this->setUser($user);

        for ($i = 1; $i < 19; $i++) {
            $courseid = ($i < 9) ? $course1->id : $course2->id;
            $instance = ($i < 9) ? $instance1->id : $instance2->id;
            $records[] = $this->create_calendar_event(
                sprintf('Event %d', $i),
                $user->id,
                'user',
                0,
                1,
                [
                    'type' => CALENDAR_EVENT_TYPE_ACTION,
                    'courseid' => $courseid,
                    'timesort' => $i + 1,
                    'modulename' => 'assign',
                    'instance' => $instance,
                ]
            );
        }

        $result = core_calendar_external::get_calendar_action_events_by_course($course1->id, null, 5);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_course_returns(),
            $result
        );
        $result = $result['events'];

        $this->assertCount(4, $result);
        $this->assertEquals('Event 1', $result[0]['name']);
        $this->assertEquals('Event 2', $result[1]['name']);
        $this->assertEquals('Event 3', $result[2]['name']);
        $this->assertEquals('Event 4', $result[3]['name']);

        $result = core_calendar_external::get_calendar_action_events_by_course($course1->id, null, 1);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_course_returns(),
            $result
        );
        $result = $result['events'];

        $this->assertEmpty($result);
    }

    /**
     * Requesting calendar events for a course and within a given time range should
     * return all events with a sort time between the lower and upper time bound
     * (inclusive).
     *
     * If there are no events in the given time range then an empty result set should be
     * returned.
     */
    public function test_get_calendar_action_events_by_course_time_range() {
        $user = $this->getDataGenerator()->create_user();
        $course1 = $this->getDataGenerator()->create_course();
        $course2 = $this->getDataGenerator()->create_course();
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
        $instance1 = $generator->create_instance(['course' => $course1->id]);
        $instance2 = $generator->create_instance(['course' => $course2->id]);
        $records = [];

        $this->getDataGenerator()->enrol_user($user->id, $course1->id);
        $this->getDataGenerator()->enrol_user($user->id, $course2->id);
        $this->resetAfterTest(true);
        $this->setUser($user);

        for ($i = 1; $i < 19; $i++) {
            $courseid = ($i < 9) ? $course1->id : $course2->id;
            $instance = ($i < 9) ? $instance1->id : $instance2->id;
            $records[] = $this->create_calendar_event(
                sprintf('Event %d', $i),
                $user->id,
                'user',
                0,
                1,
                [
                    'type' => CALENDAR_EVENT_TYPE_ACTION,
                    'courseid' => $courseid,
                    'timesort' => $i,
                    'modulename' => 'assign',
                    'instance' => $instance,
                ]
            );
        }

        $result = core_calendar_external::get_calendar_action_events_by_course($course1->id, 3, 6);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_course_returns(),
            $result
        );
        $result = $result['events'];

        $this->assertCount(4, $result);
        $this->assertEquals('Event 3', $result[0]['name']);
        $this->assertEquals('Event 4', $result[1]['name']);
        $this->assertEquals('Event 5', $result[2]['name']);
        $this->assertEquals('Event 6', $result[3]['name']);

        $result = core_calendar_external::get_calendar_action_events_by_course($course1->id, 10, 15);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_course_returns(),
            $result
        );
        $result = $result['events'];

        $this->assertEmpty($result);
    }

    /**
     * Requesting calendar events for a course and within a given time range and a limit
     * and offset should return the number of events up to the given limit value that have
     * a sort time between the lower and uppper time bound (inclusive) where the result
     * set is shifted by the offset value.
     *
     * If there are no events in the given time range then an empty result set should be
     * returned.
     */
    public function test_get_calendar_action_events_by_course_time_limit_offset() {
        $user = $this->getDataGenerator()->create_user();
        $course1 = $this->getDataGenerator()->create_course();
        $course2 = $this->getDataGenerator()->create_course();
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
        $instance1 = $generator->create_instance(['course' => $course1->id]);
        $instance2 = $generator->create_instance(['course' => $course2->id]);
        $records = [];

        $this->getDataGenerator()->enrol_user($user->id, $course1->id);
        $this->getDataGenerator()->enrol_user($user->id, $course2->id);
        $this->resetAfterTest(true);
        $this->setUser($user);

        for ($i = 1; $i < 19; $i++) {
            $courseid = ($i < 9) ? $course1->id : $course2->id;
            $instance = ($i < 9) ? $instance1->id : $instance2->id;
            $records[] = $this->create_calendar_event(
                sprintf('Event %d', $i),
                $user->id,
                'user',
                0,
                1,
                [
                    'type' => CALENDAR_EVENT_TYPE_ACTION,
                    'courseid' => $courseid,
                    'timesort' => $i,
                    'modulename' => 'assign',
                    'instance' => $instance,
                ]
            );
        }

        $result = core_calendar_external::get_calendar_action_events_by_course(
            $course1->id, 2, 7, $records[2]->id, 2);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_course_returns(),
            $result
        );
        $result = $result['events'];

        $this->assertCount(2, $result);
        $this->assertEquals('Event 4', $result[0]['name']);
        $this->assertEquals('Event 5', $result[1]['name']);

        $result = core_calendar_external::get_calendar_action_events_by_course(
            $course1->id, 2, 7, $records[4]->id, 2);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_course_returns(),
            $result
        );
        $result = $result['events'];

        $this->assertCount(2, $result);
        $this->assertEquals('Event 6', $result[0]['name']);
        $this->assertEquals('Event 7', $result[1]['name']);

        $result = core_calendar_external::get_calendar_action_events_by_course(
            $course1->id, 2, 7, $records[6]->id, 2);
        $result = \external_api::clean_returnvalue(
            core_calendar_external::get_calendar_action_events_by_course_returns(),
            $result
        );
        $result = $result['events'];

        $this->assertEmpty($result);
    }

    /**
> * Test get_calendar_action_events_by_course with search feature * Test that get_action_events_by_courses will return a list of events for each > */ * course you provided as long as the user is enrolled in the course. > public function test_get_calendar_action_events_by_course_with_search() { */ > // Generate data. public function test_get_action_events_by_courses() { > $user = $this->getDataGenerator()->create_user(); $user = $this->getDataGenerator()->create_user(); > $course = $this->getDataGenerator()->create_course(); $course1 = $this->getDataGenerator()->create_course(); > $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); $course2 = $this->getDataGenerator()->create_course(); > $instance = $generator->create_instance(['course' => $course->id]); $course3 = $this->getDataGenerator()->create_course(); > $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); > // Enrol. $instance1 = $generator->create_instance(['course' => $course1->id]); > $this->getDataGenerator()->enrol_user($user->id, $course->id); $instance2 = $generator->create_instance(['course' => $course2->id]); > $this->resetAfterTest(true); $instance3 = $generator->create_instance(['course' => $course3->id]); > $this->setUser($user); $records = []; > $mapresult = function($result) { > for ($i = 1; $i < 5; $i++) { $groupedbycourse = []; > $this->create_calendar_event( foreach ($result['groupedbycourse'] as $group) { > sprintf('Event %d', $i), $events = $group['events']; > $user->id, $courseid = $group['courseid']; > 'user', $groupedbycourse[$courseid] = $events; > 0, } > 1, > [ return $groupedbycourse; > 'type' => CALENDAR_EVENT_TYPE_ACTION, }; > 'courseid' => $course->id, > 'timesort' => $i, $this->getDataGenerator()->enrol_user($user->id, $course1->id); > 'modulename' => 'assign', $this->getDataGenerator()->enrol_user($user->id, $course2->id); > 'instance' => $instance->id, $this->resetAfterTest(true); > ] $this->setUser($user); > ); > } for ($i = 1; $i < 10; $i++) { > if ($i < 3) { > // No result found for fake search. $courseid = $course1->id; > $result = core_calendar_external::get_calendar_action_events_by_course($course->id, null, null, 0, 20, 'Fake search'); $instance = $instance1->id; > $result = \external_api::clean_returnvalue( } else if ($i < 6) { > core_calendar_external::get_calendar_action_events_by_course_returns(), $courseid = $course2->id; > $result $instance = $instance2->id; > ); } else { > $result = $result['events']; $courseid = $course3->id; > $this->assertEmpty($result); $instance = $instance3->id; > } > // Search for event name called 'Event 1'. > $result = core_calendar_external::get_calendar_action_events_by_course($course->id, null, null, 0, 20, 'Event 1'); $records[] = $this->create_calendar_event( > $result = \external_api::clean_returnvalue( sprintf('Event %d', $i), > core_calendar_external::get_calendar_action_events_by_course_returns(), $user->id, > $result 'user', > ); 0, > $result = $result['events']; 1, > $this->assertCount(1, $result); [ > $this->assertEquals('Event 1', $result[0]['name']); 'type' => CALENDAR_EVENT_TYPE_ACTION, > 'courseid' => $courseid, > // Search for activity type called 'assign'. 'timesort' => $i, > $result = core_calendar_external::get_calendar_action_events_by_course($course->id, null, null, 0, 20, 'assign'); 'modulename' => 'assign', > $result = \external_api::clean_returnvalue( 'instance' => $instance, > core_calendar_external::get_calendar_action_events_by_course_returns(), ] > $result ); > ); } > $result = $result['events']; > $this->assertCount(4, $result); $result = core_calendar_external::get_calendar_action_events_by_courses([], 1); > $this->assertEquals('Event 1', $result[0]['name']); $result = \external_api::clean_returnvalue( > $this->assertEquals('Event 2', $result[1]['name']); core_calendar_external::get_calendar_action_events_by_courses_returns(), > $this->assertEquals('Event 3', $result[2]['name']); $result > $this->assertEquals('Event 4', $result[3]['name']); ); > } $result = $result['groupedbycourse']; > > /**
$this->assertEmpty($result); $result = core_calendar_external::get_calendar_action_events_by_courses([$course1->id], 3); $result = \external_api::clean_returnvalue( core_calendar_external::get_calendar_action_events_by_courses_returns(), $result ); $groupedbycourse = $mapresult($result); $this->assertEmpty($groupedbycourse[$course1->id]); $result = core_calendar_external::get_calendar_action_events_by_courses([$course1->id], 1); $result = \external_api::clean_returnvalue( core_calendar_external::get_calendar_action_events_by_courses_returns(), $result ); $groupedbycourse = $mapresult($result); $this->assertCount(2, $groupedbycourse[$course1->id]); $this->assertEquals('Event 1', $groupedbycourse[$course1->id][0]['name']); $this->assertEquals('Event 2', $groupedbycourse[$course1->id][1]['name']); $result = core_calendar_external::get_calendar_action_events_by_courses( [$course1->id, $course2->id], 1); $result = \external_api::clean_returnvalue( core_calendar_external::get_calendar_action_events_by_courses_returns(), $result ); $groupedbycourse = $mapresult($result); $this->assertCount(2, $groupedbycourse[$course1->id]); $this->assertEquals('Event 1', $groupedbycourse[$course1->id][0]['name']); $this->assertEquals('Event 2', $groupedbycourse[$course1->id][1]['name']); $this->assertCount(3, $groupedbycourse[$course2->id]); $this->assertEquals('Event 3', $groupedbycourse[$course2->id][0]['name']); $this->assertEquals('Event 4', $groupedbycourse[$course2->id][1]['name']); $this->assertEquals('Event 5', $groupedbycourse[$course2->id][2]['name']); $result = core_calendar_external::get_calendar_action_events_by_courses( [$course1->id, $course2->id], 2, 4); $result = \external_api::clean_returnvalue( core_calendar_external::get_calendar_action_events_by_courses_returns(), $result ); $groupedbycourse = $mapresult($result); $this->assertCount(2, $groupedbycourse); $this->assertCount(1, $groupedbycourse[$course1->id]); $this->assertEquals('Event 2', $groupedbycourse[$course1->id][0]['name']); $this->assertCount(2, $groupedbycourse[$course2->id]); $this->assertEquals('Event 3', $groupedbycourse[$course2->id][0]['name']); $this->assertEquals('Event 4', $groupedbycourse[$course2->id][1]['name']); $result = core_calendar_external::get_calendar_action_events_by_courses( [$course1->id, $course2->id], 1, null, 1); $result = \external_api::clean_returnvalue( core_calendar_external::get_calendar_action_events_by_courses_returns(), $result ); $groupedbycourse = $mapresult($result); $this->assertCount(2, $groupedbycourse); $this->assertCount(1, $groupedbycourse[$course1->id]); $this->assertEquals('Event 1', $groupedbycourse[$course1->id][0]['name']); $this->assertCount(1, $groupedbycourse[$course2->id]); $this->assertEquals('Event 3', $groupedbycourse[$course2->id][0]['name']);
> } } > > /** /** > * Test get_action_events_by_courses with search feature * Test for deleting module events. > */ */ > public function test_get_action_events_by_courses_with_search() { public function test_delete_calendar_events_for_modules() { > // Generate data. $this->resetAfterTest(); > $user = $this->getDataGenerator()->create_user(); $this->setAdminUser(); > $course1 = $this->getDataGenerator()->create_course(); $course = $this->getDataGenerator()->create_course(); > $course2 = $this->getDataGenerator()->create_course(); $nexttime = time() + DAYSECS; > $course3 = $this->getDataGenerator()->create_course(); $this->getDataGenerator()->create_module('assign', ['course' => $course->id, 'duedate' => $nexttime]); > $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); $events = calendar_get_events(time(), $nexttime, true, true, true); > $instance1 = $generator->create_instance(['course' => $course1->id]); $this->assertCount(1, $events); > $instance2 = $generator->create_instance(['course' => $course2->id]); $params = []; > $instance3 = $generator->create_instance(['course' => $course3->id]); foreach ($events as $event) { > $params[] = [ > $this->getDataGenerator()->enrol_user($user->id, $course1->id); 'eventid' => $event->id, > $this->getDataGenerator()->enrol_user($user->id, $course2->id); 'repeat' => false > $this->resetAfterTest(true); ]; > $this->setUser($user); } > > $mapresult = function($result) { $this->expectException(\moodle_exception::class); > $groupedbycourse = []; core_calendar_external::delete_calendar_events($params); > foreach ($result['groupedbycourse'] as $group) { } > $events = $group['events']; > $courseid = $group['courseid']; /** > $groupedbycourse[$courseid] = $events; * Updating the event start day should change the date value but leave > } * the time of day unchanged. > */ > return $groupedbycourse; public function test_update_event_start_day() { > }; $generator = $this->getDataGenerator(); > $user = $generator->create_user(); > for ($i = 1; $i < 10; $i++) { $roleid = $generator->create_role(); > if ($i < 3) { $context = \context_system::instance(); > $courseid = $course1->id; $originalstarttime = new \DateTimeImmutable('2017-01-1T15:00:00+08:00'); > $instance = $instance1->id; $newstartdate = new \DateTimeImmutable('2018-02-2T10:00:00+08:00'); > } else if ($i < 6) { $expected = new \DateTimeImmutable('2018-02-2T15:00:00+08:00'); > $courseid = $course2->id; > $instance = $instance2->id; $generator->role_assign($roleid, $user->id, $context->id); > } else { assign_capability('moodle/calendar:manageownentries', CAP_ALLOW, $roleid, $context, true); > $courseid = $course3->id; > $instance = $instance3->id; $this->setUser($user); > } $this->resetAfterTest(true); > > $records[] = $this->create_calendar_event( $event = $this->create_calendar_event( > sprintf('Event %d', $i), 'Test event', > $user->id, $user->id, > 'user', 'user', > 0, 0, > 1, null, > [ [ > 'type' => CALENDAR_EVENT_TYPE_ACTION, 'courseid' => 0, > 'courseid' => $courseid, 'timestart' => $originalstarttime->getTimestamp() > 'timesort' => $i, ] > 'modulename' => 'assign', ); > 'instance' => $instance, > ] $result = core_calendar_external::update_event_start_day($event->id, $newstartdate->getTimestamp()); > ); $result = \external_api::clean_returnvalue( > } core_calendar_external::update_event_start_day_returns(), > $result > // No result found for fake search. ); > $result = core_calendar_external::get_calendar_action_events_by_courses([$course1->id, $course2->id, $course3->id], > 1, null, 20, 'Fake search'); $this->assertEquals($expected->getTimestamp(), $result['event']['timestart']); > $result = \external_api::clean_returnvalue( } > core_calendar_external::get_calendar_action_events_by_courses_returns(), > $result /** > ); * A user should not be able to edit an event that they don't have > $groupedbycourse = $mapresult($result); * capabilities for. > */ > $this->assertEmpty($groupedbycourse[$course1->id]); public function test_update_event_start_day_no_permission() { > $this->assertEmpty($groupedbycourse[$course2->id]); $generator = $this->getDataGenerator(); > $this->assertArrayNotHasKey($course3->id, $groupedbycourse); $user = $generator->create_user(); > $roleid = $generator->create_role(); > // Search for event name called 'Event 1'. $context = \context_system::instance(); > $result = core_calendar_external::get_calendar_action_events_by_courses([$course1->id, $course2->id, $course3->id], $originalstarttime = new \DateTimeImmutable('2017-01-1T15:00:00+08:00'); > 1, null, 20, 'Event 1'); $newstartdate = new \DateTimeImmutable('2018-02-2T10:00:00+08:00'); > $result = \external_api::clean_returnvalue( $expected = new \DateTimeImmutable('2018-02-2T15:00:00+08:00'); > core_calendar_external::get_calendar_action_events_by_courses_returns(), > $result $generator->role_assign($roleid, $user->id, $context->id); > ); assign_capability('moodle/calendar:manageownentries', CAP_ALLOW, $roleid, $context, true); > $groupedbycourse = $mapresult($result); > $this->setUser($user); > $this->assertArrayNotHasKey($course3->id, $groupedbycourse); $this->resetAfterTest(true); > $this->assertCount(2, $groupedbycourse); > $this->assertCount(1, $groupedbycourse[$course1->id]); $event = $this->create_calendar_event( > $this->assertCount(0, $groupedbycourse[$course2->id]); 'Test event', > $this->assertEquals('Event 1', $groupedbycourse[$course1->id][0]['name']); $user->id, > 'user', > // Search for activity type called 'assign'. 0, > $result = core_calendar_external::get_calendar_action_events_by_courses([$course1->id, $course2->id, $course3->id], null, > 1, null, 20, 'assign'); [ > $result = \external_api::clean_returnvalue( 'courseid' => 0, > core_calendar_external::get_calendar_action_events_by_courses_returns(), 'timestart' => $originalstarttime->getTimestamp() > $result ] > ); ); > $groupedbycourse = $mapresult($result); > assign_capability('moodle/calendar:manageownentries', CAP_PROHIBIT, $roleid, $context, true); > $this->assertArrayNotHasKey($course3->id, $groupedbycourse); $this->expectException(\moodle_exception::class); > $this->assertCount(2, $groupedbycourse); $result = core_calendar_external::update_event_start_day($event->id, $newstartdate->getTimestamp()); > $this->assertCount(2, $groupedbycourse[$course1->id]); $result = \external_api::clean_returnvalue( > $this->assertCount(3, $groupedbycourse[$course2->id]); core_calendar_external::update_event_start_day_returns(), > $this->assertEquals('Event 1', $groupedbycourse[$course1->id][0]['name']); $result > $this->assertEquals('Event 2', $groupedbycourse[$course1->id][1]['name']); ); > $this->assertEquals('Event 3', $groupedbycourse[$course2->id][0]['name']); } > $this->assertEquals('Event 4', $groupedbycourse[$course2->id][1]['name']); > $this->assertEquals('Event 5', $groupedbycourse[$course2->id][2]['name']);
/** * A user should not be able to update a module event. */ public function test_update_event_start_day_module_event() { $generator = $this->getDataGenerator(); $user = $generator->create_user(); $course = $generator->create_course(); $plugingenerator = $generator->get_plugin_generator('mod_assign'); $moduleinstance = $plugingenerator->create_instance(['course' => $course->id]); $roleid = $generator->create_role(); $context = \context_course::instance($course->id); $originalstarttime = new \DateTimeImmutable('2017-01-1T15:00:00+08:00'); $newstartdate = new \DateTimeImmutable('2018-02-2T10:00:00+08:00'); $expected = new \DateTimeImmutable('2018-02-2T15:00:00+08:00'); $generator->role_assign($roleid, $user->id, $context->id); $generator->enrol_user($user->id, $course->id); $this->setUser($user); $this->resetAfterTest(true); $event = $this->create_calendar_event( 'Test event', $user->id, 'user', 0, null, [ 'modulename' => 'assign', 'instance' => $moduleinstance->id, 'courseid' => $course->id, 'timestart' => $originalstarttime->getTimestamp() ] ); assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true); $this->expectException(\moodle_exception::class); $result = core_calendar_external::update_event_start_day($event->id, $newstartdate->getTimestamp()); $result = \external_api::clean_returnvalue( core_calendar_external::update_event_start_day_returns(), $result ); } /** * Submit a request where the time duration until is earlier than the time * start in order to get a validation error from the server. */ public function test_submit_create_update_form_validation_error() { $user = $this->getDataGenerator()->create_user(); $timestart = new \DateTime(); $interval = new \DateInterval("P1D"); // One day. $timedurationuntil = new \DateTime(); $timedurationuntil->sub($interval); $formdata = [ 'id' => 0, 'userid' => $user->id, 'modulename' => '', 'instance' => 0, 'visible' => 1, 'name' => 'Test', 'timestart' => [ 'day' => $timestart->format('j'), 'month' => $timestart->format('n'), 'year' => $timestart->format('Y'), 'hour' => $timestart->format('G'), 'minute' => 0, ], 'eventtype' => 'user', 'description' => [ 'text' => '', 'format' => 1, ], 'location' => 'Test', 'duration' => 1, 'timedurationuntil' => [ 'day' => $timedurationuntil->format('j'), 'month' => $timedurationuntil->format('n'), 'year' => $timedurationuntil->format('Y'), 'hour' => $timedurationuntil->format('G'), 'minute' => 0, ] ]; $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata); $querystring = http_build_query($formdata, '', '&amp;'); $this->resetAfterTest(true); $this->setUser($user); $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']); } /** * A user with the moodle/calendar:manageownentries capability at the * system context should be able to create a user event. */ public function test_submit_create_update_form_create_user_event() { $generator = $this->getDataGenerator(); $user = $generator->create_user(); $roleid = $generator->create_role(); $context = \context_system::instance(); $timestart = new \DateTime(); $interval = new \DateInterval("P1D"); // One day. $timedurationuntil = new \DateTime(); $timedurationuntil->add($interval); $formdata = [ 'id' => 0, 'userid' => $user->id, 'modulename' => '', 'instance' => 0, 'visible' => 1, 'name' => 'Test', 'timestart' => [ 'day' => $timestart->format('j'), 'month' => $timestart->format('n'), 'year' => $timestart->format('Y'), 'hour' => $timestart->format('G'), 'minute' => 0, ], 'eventtype' => 'user', 'description' => [ 'text' => '', 'format' => 1, 'itemid' => 0 ], 'location' => 'Test', 'duration' => 1, 'timedurationuntil' => [ 'day' => $timedurationuntil->format('j'), 'month' => $timedurationuntil->format('n'), 'year' => $timedurationuntil->format('Y'), 'hour' => $timedurationuntil->format('G'), 'minute' => 0, ] ]; $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata); $querystring = http_build_query($formdata, '', '&'); $generator->role_assign($roleid, $user->id, $context->id); assign_capability('moodle/calendar:manageownentries', CAP_ALLOW, $roleid, $context, true); $user->ignoresesskey = true; $this->resetAfterTest(true); $this->setUser($user); $result = \external_api::clean_returnvalue( core_calendar_external::submit_create_update_form_returns(), core_calendar_external::submit_create_update_form($querystring) ); $event = $result['event']; $this->assertEquals($user->id, $event['userid']); $this->assertEquals($formdata['eventtype'], $event['eventtype']); $this->assertEquals($formdata['name'], $event['name']); } /** * A user without the moodle/calendar:manageownentries capability at the * system context should not be able to create a user event. */ public function test_submit_create_update_form_create_user_event_no_permission() { $generator = $this->getDataGenerator(); $user = $generator->create_user(); $roleid = $generator->create_role(); $context = \context_system::instance(); $timestart = new \DateTime(); $interval = new \DateInterval("P1D"); // One day. $timedurationuntil = new \DateTime(); $timedurationuntil->add($interval); $formdata = [ 'id' => 0, 'userid' => $user->id, 'modulename' => '', 'instance' => 0, 'visible' => 1, 'name' => 'Test', 'timestart' => [ 'day' => $timestart->format('j'), 'month' => $timestart->format('n'), 'year' => $timestart->format('Y'), 'hour' => $timestart->format('G'), 'minute' => 0, ], 'eventtype' => 'user', 'description' => [ 'text' => '', 'format' => 1, ], 'location' => 'Test', 'duration' => 1, 'timedurationuntil' => [ 'day' => $timedurationuntil->format('j'), 'month' => $timedurationuntil->format('n'), 'year' => $timedurationuntil->format('Y'), 'hour' => $timedurationuntil->format('G'), 'minute' => 0, ] ]; $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata); $querystring = http_build_query($formdata, '', '&'); $generator->role_assign($roleid, $user->id, $context->id); assign_capability('moodle/calendar:manageownentries', CAP_PROHIBIT, $roleid, $context, true); $user->ignoresesskey = true; $this->resetAfterTest(true); $this->setUser($user); $this->expectException(\moodle_exception::class); \external_api::clean_returnvalue( core_calendar_external::submit_create_update_form_returns(), core_calendar_external::submit_create_update_form($querystring) ); } /** * A user with the moodle/calendar:manageentries capability at the * site course context should be able to create a site event. */ public function test_submit_create_update_form_create_site_event() { $generator = $this->getDataGenerator(); $user = $generator->create_user(); $context = \context_system::instance(); $roleid = $generator->create_role(); $timestart = new \DateTime(); $interval = new \DateInterval("P1D"); // One day. $timedurationuntil = new \DateTime(); $timedurationuntil->add($interval); $formdata = [ 'id' => 0, 'userid' => $user->id, 'modulename' => '', 'instance' => 0, 'visible' => 1, 'name' => 'Test', 'timestart' => [ 'day' => $timestart->format('j'), 'month' => $timestart->format('n'), 'year' => $timestart->format('Y'), 'hour' => $timestart->format('G'), 'minute' => 0, ], 'eventtype' => 'site', 'description' => [ 'text' => '', 'format' => 1, 'itemid' => 0 ], 'location' => 'Test', 'duration' => 1, 'timedurationuntil' => [ 'day' => $timedurationuntil->format('j'), 'month' => $timedurationuntil->format('n'), 'year' => $timedurationuntil->format('Y'), 'hour' => $timedurationuntil->format('G'), 'minute' => 0, ] ]; $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata); $querystring = http_build_query($formdata, '', '&'); $generator->role_assign($roleid, $user->id, $context->id); assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true); $user->ignoresesskey = true; $this->resetAfterTest(true); $this->setUser($user); $result = \external_api::clean_returnvalue( core_calendar_external::submit_create_update_form_returns(), core_calendar_external::submit_create_update_form($querystring) ); $event = $result['event']; $this->assertEquals($user->id, $event['userid']); $this->assertEquals($formdata['eventtype'], $event['eventtype']); $this->assertEquals($formdata['name'], $event['name']); } /** * A user without the moodle/calendar:manageentries capability at the * site course context should not be able to create a site event. */ public function test_submit_create_update_form_create_site_event_no_permission() { $generator = $this->getDataGenerator(); $user = $generator->create_user(); $context = \context_course::instance(SITEID); $roleid = $generator->create_role(); $timestart = new \DateTime(); $interval = new \DateInterval("P1D"); // One day. $timedurationuntil = new \DateTime(); $timedurationuntil->add($interval); $formdata = [ 'id' => 0, 'userid' => $user->id, 'modulename' => '', 'instance' => 0, 'visible' => 1, 'name' => 'Test', 'timestart' => [ 'day' => $timestart->format('j'), 'month' => $timestart->format('n'), 'year' => $timestart->format('Y'), 'hour' => $timestart->format('G'), 'minute' => 0, ], 'eventtype' => 'site', 'description' => [ 'text' => '', 'format' => 1, ], 'location' => 'Test', 'duration' => 1, 'timedurationuntil' => [ 'day' => $timedurationuntil->format('j'), 'month' => $timedurationuntil->format('n'), 'year' => $timedurationuntil->format('Y'), 'hour' => $timedurationuntil->format('G'), 'minute' => 0, ] ]; $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata); $querystring = http_build_query($formdata, '', '&'); $generator->role_assign($roleid, $user->id, $context->id); assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true); $user->ignoresesskey = true; $this->resetAfterTest(true); $this->setUser($user); $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']); } /** * A user that has the moodle/calendar:manageentries in a course that they * are enrolled in should be able to create a course event in that course. */ public function test_submit_create_update_form_create_course_event() { $generator = $this->getDataGenerator(); $user = $generator->create_user(); $course = $generator->create_course(); $context = \context_course::instance($course->id); $roleid = $generator->create_role(); $timestart = new \DateTime(); $interval = new \DateInterval("P1D"); // One day. $timedurationuntil = new \DateTime(); $timedurationuntil->add($interval); $formdata = [ 'id' => 0, 'userid' => $user->id, 'modulename' => '', 'instance' => 0, 'visible' => 1, 'name' => 'Test', 'timestart' => [ 'day' => $timestart->format('j'), 'month' => $timestart->format('n'), 'year' => $timestart->format('Y'), 'hour' => $timestart->format('G'), 'minute' => 0, ], 'eventtype' => 'course', 'courseid' => $course->id, 'description' => [ 'text' => '', 'format' => 1, 'itemid' => 0, ], 'location' => 'Test', 'duration' => 1, 'timedurationuntil' => [ 'day' => $timedurationuntil->format('j'), 'month' => $timedurationuntil->format('n'), 'year' => $timedurationuntil->format('Y'), 'hour' => $timedurationuntil->format('G'), 'minute' => 0, ] ]; $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata); $querystring = http_build_query($formdata, '', '&'); $generator->enrol_user($user->id, $course->id, 'student'); $generator->role_assign($roleid, $user->id, $context->id); assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true); $user->ignoresesskey = true; $this->resetAfterTest(true); $this->setUser($user); $result = \external_api::clean_returnvalue( core_calendar_external::submit_create_update_form_returns(), core_calendar_external::submit_create_update_form($querystring) ); $event = $result['event']; $this->assertEquals($user->id, $event['userid']); $this->assertEquals($formdata['eventtype'], $event['eventtype']); $this->assertEquals($formdata['name'], $event['name']); $this->assertEquals($formdata['courseid'], $event['course']['id']); } /** * A user without the moodle/calendar:manageentries capability in a course * that they are enrolled in should not be able to create a course event in that course. */ public function test_submit_create_update_form_create_course_event_no_permission() { $generator = $this->getDataGenerator(); $user = $generator->create_user(); $course = $generator->create_course(); $context = \context_course::instance($course->id); $roleid = $generator->create_role(); $timestart = new \DateTime(); $interval = new \DateInterval("P1D"); // One day. $timedurationuntil = new \DateTime(); $timedurationuntil->add($interval); $formdata = [ 'id' => 0, 'userid' => $user->id, 'modulename' => '', 'instance' => 0, 'visible' => 1, 'name' => 'Test', 'timestart' => [ 'day' => $timestart->format('j'), 'month' => $timestart->format('n'), 'year' => $timestart->format('Y'), 'hour' => $timestart->format('G'), 'minute' => 0, ], 'eventtype' => 'course', 'courseid' => $course->id, 'description' => [ 'text' => '', 'format' => 1, ], 'location' => 'Test', 'duration' => 1, 'timedurationuntil' => [ 'day' => $timedurationuntil->format('j'), 'month' => $timedurationuntil->format('n'), 'year' => $timedurationuntil->format('Y'), 'hour' => $timedurationuntil->format('G'), 'minute' => 0, ] ]; $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata); $querystring = http_build_query($formdata, '', '&'); $generator->enrol_user($user->id, $course->id, 'student'); $generator->role_assign($roleid, $user->id, $context->id); assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true); $user->ignoresesskey = true; $this->resetAfterTest(true); $this->setUser($user); $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']); } /** * A user should not be able to create an event for a course that they are * not enrolled in. */ public function test_submit_create_update_form_create_course_event_not_enrolled() { $generator = $this->getDataGenerator(); $user = $generator->create_user(); $course = $generator->create_course(); $course2 = $generator->create_course(); $context = \context_course::instance($course->id); $roleid = $generator->create_role(); $timestart = new \DateTime(); $interval = new \DateInterval("P1D"); // One day. $timedurationuntil = new \DateTime(); $timedurationuntil->add($interval); $formdata = [ 'id' => 0, 'userid' => $user->id, 'modulename' => '', 'instance' => 0, 'visible' => 1, 'name' => 'Test', 'timestart' => [ 'day' => $timestart->format('j'), 'month' => $timestart->format('n'), 'year' => $timestart->format('Y'), 'hour' => $timestart->format('G'), 'minute' => 0, ], 'eventtype' => 'course', 'courseid' => $course2->id, // Not enrolled. 'description' => [ 'text' => '', 'format' => 1, ], 'location' => 'Test', 'duration' => 1, 'timedurationuntil' => [ 'day' => $timedurationuntil->format('j'), 'month' => $timedurationuntil->format('n'), 'year' => $timedurationuntil->format('Y'), 'hour' => $timedurationuntil->format('G'), 'minute' => 0, ] ]; $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata); $querystring = http_build_query($formdata, '', '&'); $generator->enrol_user($user->id, $course->id, 'student'); $generator->role_assign($roleid, $user->id, $context->id); assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true); $user->ignoresesskey = true; $this->resetAfterTest(true); $this->setUser($user); $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']); } /** * A user should be able to create an event for a group that they are a member of in * a course in which they are enrolled and have the moodle/calendar:manageentries capability. */ public function test_submit_create_update_form_create_group_event_group_member_manage_course() { $generator = $this->getDataGenerator(); $user = $generator->create_user(); $course = $generator->create_course(); $group = $generator->create_group(array('courseid' => $course->id)); $context = \context_course::instance($course->id); $roleid = $generator->create_role(); $timestart = new \DateTime(); $interval = new \DateInterval("P1D"); // One day. $timedurationuntil = new \DateTime(); $timedurationuntil->add($interval); $formdata = [ 'id' => 0, 'userid' => $user->id, 'modulename' => '', 'instance' => 0, 'visible' => 1, 'name' => 'Test', 'timestart' => [ 'day' => $timestart->format('j'), 'month' => $timestart->format('n'), 'year' => $timestart->format('Y'), 'hour' => $timestart->format('G'), 'minute' => 0, ], 'eventtype' => 'group', 'groupid' => $group->id, 'groupcourseid' => $course->id, 'description' => [ 'text' => '', 'format' => 1, 'itemid' => 0 ], 'location' => 'Test', 'duration' => 1, 'timedurationuntil' => [ 'day' => $timedurationuntil->format('j'), 'month' => $timedurationuntil->format('n'), 'year' => $timedurationuntil->format('Y'), 'hour' => $timedurationuntil->format('G'), 'minute' => 0, ] ]; $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata); $querystring = http_build_query($formdata, '', '&'); $generator->enrol_user($user->id, $course->id, 'student'); $generator->role_assign($roleid, $user->id, $context->id); $generator->create_group_member(['groupid' => $group->id, 'userid' => $user->id]); assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true); $user->ignoresesskey = true; $this->resetAfterTest(true); $this->setUser($user); $result = \external_api::clean_returnvalue( core_calendar_external::submit_create_update_form_returns(), core_calendar_external::submit_create_update_form($querystring) ); $event = $result['event']; $this->assertEquals($user->id, $event['userid']); $this->assertEquals($formdata['eventtype'], $event['eventtype']); $this->assertEquals($formdata['name'], $event['name']); $this->assertEquals($group->id, $event['groupid']); } /** * A user should be able to create an event for a group that they are a member of in * a course in which they are enrolled and have the moodle/calendar:managegroupentries capability. */ public function test_submit_create_update_form_create_group_event_group_member_manage_group_entries() { $generator = $this->getDataGenerator(); $user = $generator->create_user(); $course = $generator->create_course(); $group = $generator->create_group(array('courseid' => $course->id)); $context = \context_course::instance($course->id); $roleid = $generator->create_role(); $timestart = new \DateTime(); $interval = new \DateInterval("P1D"); // One day. $timedurationuntil = new \DateTime(); $timedurationuntil->add($interval); $formdata = [ 'id' => 0, 'userid' => $user->id, 'modulename' => '', 'instance' => 0, 'visible' => 1, 'name' => 'Test', 'timestart' => [ 'day' => $timestart->format('j'), 'month' => $timestart->format('n'), 'year' => $timestart->format('Y'), 'hour' => $timestart->format('G'), 'minute' => 0, ], 'eventtype' => 'group', 'groupid' => $group->id, 'groupcourseid' => $course->id, 'description' => [ 'text' => '', 'format' => 1, 'itemid' => 0 ], 'location' => 'Test', 'duration' => 1, 'timedurationuntil' => [ 'day' => $timedurationuntil->format('j'), 'month' => $timedurationuntil->format('n'), 'year' => $timedurationuntil->format('Y'), 'hour' => $timedurationuntil->format('G'), 'minute' => 0, ] ]; $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata); $querystring = http_build_query($formdata, '', '&'); $generator->enrol_user($user->id, $course->id, 'student'); $generator->role_assign($roleid, $user->id, $context->id); $generator->create_group_member(['groupid' => $group->id, 'userid' => $user->id]); assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true); assign_capability('moodle/calendar:managegroupentries', CAP_ALLOW, $roleid, $context, true); $user->ignoresesskey = true; $this->resetAfterTest(true); $this->setUser($user); $result = \external_api::clean_returnvalue( core_calendar_external::submit_create_update_form_returns(), core_calendar_external::submit_create_update_form($querystring) ); $event = $result['event']; $this->assertEquals($user->id, $event['userid']); $this->assertEquals($formdata['eventtype'], $event['eventtype']); $this->assertEquals($formdata['name'], $event['name']); $this->assertEquals($group->id, $event['groupid']); } /** * A user should be able to create an event for any group in a course in which * they are enrolled and have the moodle/site:accessallgroups capability. */ public function test_submit_create_update_form_create_group_event_access_all_groups() { $generator = $this->getDataGenerator(); $user = $generator->create_user(); $course = $generator->create_course(); $group = $generator->create_group(array('courseid' => $course->id)); $context = \context_course::instance($course->id); $roleid = $generator->create_role(); $timestart = new \DateTime(); $interval = new \DateInterval("P1D"); // One day. $timedurationuntil = new \DateTime(); $timedurationuntil->add($interval); $formdata = [ 'id' => 0, 'userid' => $user->id, 'modulename' => '', 'instance' => 0, 'visible' => 1, 'name' => 'Test', 'timestart' => [ 'day' => $timestart->format('j'), 'month' => $timestart->format('n'), 'year' => $timestart->format('Y'), 'hour' => $timestart->format('G'), 'minute' => 0, ], 'eventtype' => 'group', 'groupid' => $group->id, 'groupcourseid' => $course->id, 'description' => [ 'text' => '', 'format' => 1, 'itemid' => 0 ], 'location' => 'Test', 'duration' => 1, 'timedurationuntil' => [ 'day' => $timedurationuntil->format('j'), 'month' => $timedurationuntil->format('n'), 'year' => $timedurationuntil->format('Y'), 'hour' => $timedurationuntil->format('G'), 'minute' => 0, ] ]; $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata); $querystring = http_build_query($formdata, '', '&'); $generator->enrol_user($user->id, $course->id, 'student'); $generator->role_assign($roleid, $user->id, $context->id); assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true); assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $context, true); $user->ignoresesskey = true; $this->resetAfterTest(true); $this->setUser($user); $result = \external_api::clean_returnvalue( core_calendar_external::submit_create_update_form_returns(), core_calendar_external::submit_create_update_form($querystring) ); $event = $result['event']; $this->assertEquals($user->id, $event['userid']); $this->assertEquals($formdata['eventtype'], $event['eventtype']); $this->assertEquals($formdata['name'], $event['name']); $this->assertEquals($group->id, $event['groupid']); } /** * A user should not be able to create an event for any group that they are not a * member of in a course in which they are enrolled but don't have the * moodle/site:accessallgroups capability. */ public function test_submit_create_update_form_create_group_event_non_member_no_permission() { $generator = $this->getDataGenerator(); $user = $generator->create_user(); $course = $generator->create_course(); $group = $generator->create_group(array('courseid' => $course->id)); $context = \context_course::instance($course->id); $roleid = $generator->create_role(); $timestart = new \DateTime(); $interval = new \DateInterval("P1D"); // One day. $timedurationuntil = new \DateTime(); $timedurationuntil->add($interval); $formdata = [ 'id' => 0, 'userid' => $user->id, 'modulename' => '', 'instance' => 0, 'visible' => 1, 'name' => 'Test', 'timestart' => [ 'day' => $timestart->format('j'), 'month' => $timestart->format('n'), 'year' => $timestart->format('Y'), 'hour' => $timestart->format('G'), 'minute' => 0, ], 'eventtype' => 'group', 'groupid' => $group->id, 'groupcourseid' => $course->id, 'description' => [ 'text' => '', 'format' => 1, ], 'location' => 'Test', 'duration' => 1, 'timedurationuntil' => [ 'day' => $timedurationuntil->format('j'), 'month' => $timedurationuntil->format('n'), 'year' => $timedurationuntil->format('Y'), 'hour' => $timedurationuntil->format('G'), 'minute' => 0, ] ]; $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata); $querystring = http_build_query($formdata, '', '&'); $generator->enrol_user($user->id, $course->id, 'student'); $generator->role_assign($roleid, $user->id, $context->id); assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true); assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $roleid, $context, true); $user->ignoresesskey = true; $this->resetAfterTest(true); $this->setUser($user); $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']); } /** * A user should not be able load the calendar monthly view for a course they cannot access. */ public function test_get_calendar_monthly_view_no_course_permission() { global $USER; $this->resetAfterTest(true); $this->setAdminUser(); $generator = $this->getDataGenerator(); $user1 = $generator->create_user(); $user2 = $generator->create_user(); $course = $generator->create_course(); $generator->enrol_user($user1->id, $course->id, 'student'); $name = 'Course Event (course' . $course->id . ')'; $record = new \stdClass(); $record->courseid = $course->id; $courseevent = $this->create_calendar_event($name, $USER->id, 'course', 0, time(), $record); $timestart = new \DateTime(); // Admin can load the course. $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_monthly_view_returns(), core_calendar_external::get_calendar_monthly_view($timestart->format('Y'), $timestart->format('n'), $course->id, null, false, true, $timestart->format('j')) ); $this->assertEquals($data['courseid'], $course->id); // User enrolled in the course can load the course calendar. $this->setUser($user1); $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_monthly_view_returns(), core_calendar_external::get_calendar_monthly_view($timestart->format('Y'), $timestart->format('n'), $course->id, null, false, true, $timestart->format('j')) ); $this->assertEquals($data['courseid'], $course->id); // User not enrolled in the course cannot load the course calendar. $this->setUser($user2); $this->expectException(\require_login_exception::class); $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_monthly_view_returns(), core_calendar_external::get_calendar_monthly_view($timestart->format('Y'), $timestart->format('n'), $course->id, null, false, false, $timestart->format('j')) ); } /** * Test get_calendar_monthly_view when a day parameter is provided. */ public function test_get_calendar_monthly_view_with_day_provided() { $this->resetAfterTest(); $this->setAdminUser(); $timestart = new \DateTime(); $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_monthly_view_returns(), core_calendar_external::get_calendar_monthly_view($timestart->format('Y'), $timestart->format('n'), SITEID, null, false, true, $timestart->format('j')) ); $this->assertEquals($data['date']['mday'], $timestart->format('d')); } /** * A user should not be able load the calendar day view for a course they cannot access. */ public function test_get_calendar_day_view_no_course_permission() { global $USER; $this->resetAfterTest(true); $this->setAdminUser(); $generator = $this->getDataGenerator(); $user1 = $generator->create_user(); $user2 = $generator->create_user(); $course = $generator->create_course(); $generator->enrol_user($user1->id, $course->id, 'student'); $name = 'Course Event (course' . $course->id . ')'; $record = new \stdClass(); $record->courseid = $course->id; $courseevent = $this->create_calendar_event($name, $USER->id, 'course', 0, time(), $record); $timestart = new \DateTime(); // Admin can load the course. $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_day_view_returns(), core_calendar_external::get_calendar_day_view($timestart->format('Y'), $timestart->format('n'), $timestart->format('j'), $course->id, null) ); $this->assertEquals($data['courseid'], $course->id); // User enrolled in the course can load the course calendar. $this->setUser($user1); $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_day_view_returns(), core_calendar_external::get_calendar_day_view($timestart->format('Y'), $timestart->format('n'), $timestart->format('j'), $course->id, null) ); $this->assertEquals($data['courseid'], $course->id); // User not enrolled in the course cannot load the course calendar. $this->setUser($user2); $this->expectException(\require_login_exception::class); $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_day_view_returns(), core_calendar_external::get_calendar_day_view($timestart->format('Y'), $timestart->format('n'), $timestart->format('j'), $course->id, null) ); } /** * A user should not be able load the calendar upcoming view for a course they cannot access. */ public function test_get_calendar_upcoming_view_no_course_permission() { global $USER; $this->resetAfterTest(true); $this->setAdminUser(); $generator = $this->getDataGenerator(); $user1 = $generator->create_user(); $user2 = $generator->create_user(); $course = $generator->create_course(); $generator->enrol_user($user1->id, $course->id, 'student'); $name = 'Course Event (course' . $course->id . ')'; $record = new \stdClass(); $record->courseid = $course->id; $courseevent = $this->create_calendar_event($name, $USER->id, 'course', 0, time(), $record); // Admin can load the course. $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_upcoming_view_returns(), core_calendar_external::get_calendar_upcoming_view($course->id, null) ); $this->assertEquals($data['courseid'], $course->id); // User enrolled in the course can load the course calendar. $this->setUser($user1); $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_upcoming_view_returns(), core_calendar_external::get_calendar_upcoming_view($course->id, null) ); $this->assertEquals($data['courseid'], $course->id); // User not enrolled in the course cannot load the course calendar. $this->setUser($user2); $this->expectException(\require_login_exception::class); $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_upcoming_view_returns(), core_calendar_external::get_calendar_upcoming_view($course->id, null) ); } /** * A user should not be able load the calendar event for a course they cannot access. */ public function test_get_calendar_event_by_id_no_course_permission() { global $USER; $this->resetAfterTest(true); $this->setAdminUser(); $generator = $this->getDataGenerator(); $user1 = $generator->create_user(); $user2 = $generator->create_user(); $course = $generator->create_course(); $generator->enrol_user($user1->id, $course->id, 'student'); $name = 'Course Event (course' . $course->id . ')'; $record = new \stdClass(); $record->courseid = $course->id; $courseevent = $this->create_calendar_event($name, $USER->id, 'course', 0, time(), $record); // Admin can load the course event. $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_event_by_id_returns(), core_calendar_external::get_calendar_event_by_id($courseevent->id) ); $this->assertEquals($data['event']['id'], $courseevent->id); // User enrolled in the course can load the course event. $this->setUser($user1); $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_event_by_id_returns(), core_calendar_external::get_calendar_event_by_id($courseevent->id) ); $this->assertEquals($data['event']['id'], $courseevent->id); // User not enrolled in the course cannot load the course event. $this->setUser($user2); $this->expectException(\moodle_exception::class); $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_event_by_id_returns(), core_calendar_external::get_calendar_event_by_id($courseevent->id) ); } /** * User data for testing reading calendar events. * * @return array */ public function get_calendar_event_by_id_prevent_read_other_users_events_data_provider(): array { $syscontext = \context_system::instance(); $managerrole = 'manager'; return [ [true, false, $syscontext, $managerrole, true], [false, false, $syscontext, $managerrole, false], [false, false, null, null, true], [false, true, null, null, false], ]; } /** * Prevent user from reading other user's event. * * @covers \core_calendar_external::get_calendar_event_by_id * @dataProvider get_calendar_event_by_id_prevent_read_other_users_events_data_provider * * @param bool $isadminevent Is admin's event * @param bool $isadmin Is current user admin user * @param null|stdClass $readerrolecontext Reader role context * @param null|string $readerrolename Role name * @param bool $expectexception Should the test throw exception */ public function test_get_calendar_event_by_id_prevent_read_other_users_events( bool $isadminevent, bool $isadmin, ?\stdClass $readerrolecontext, ?string $readerrolename, bool $expectexception) { global $USER, $DB; $this->resetAfterTest(); $generator = $this->getDataGenerator(); if ($isadminevent) { $this->setAdminUser(); } else { $user = $generator->create_user(); $this->setUser($user); } $userevent = $this->create_calendar_event('user event', $USER->id, 'user', 0, time()); $results = \external_api::clean_returnvalue( core_calendar_external::get_calendar_event_by_id_returns(), core_calendar_external::get_calendar_event_by_id($userevent->id) ); $event = reset($results); $this->assertEquals($userevent->id, $event['id']); if ($isadmin) { $this->setAdminUser(); } else { $reader = $generator->create_user(); if ($readerrolename && $readerrolecontext) { $managerroleid = $DB->get_field('role', 'id', ['shortname' => $readerrolename]); role_assign($managerroleid, $reader->id, $readerrolecontext->id); } $this->setUser($reader); } if ($expectexception) { // Setup if exception is expected for the test. $this->expectException(\moodle_exception::class); } \external_api::clean_returnvalue( core_calendar_external::get_calendar_event_by_id_returns(), core_calendar_external::get_calendar_event_by_id($userevent->id) ); } /** * User data for testing editing or deleting calendar events. * * @return array */ public function edit_or_delete_other_users_events_data_provider(): array { $syscontext = \context_system::instance(); $managerrole = 'manager'; return [ [false, false, $syscontext, $managerrole, false], [false, true, $syscontext, $managerrole, true], [false, false, null, null, true], [true, false, null, null, false], ]; } /** * Test the behavior of deleting other users' user events. * * @dataProvider edit_or_delete_other_users_events_data_provider * @covers \core_calendar_external::delete_calendar_events * @param bool $isadmin Whether the current user is admin. * @param bool $isadminevent Whether it's an admin event or not. * @param stdClass|null $writerrolecontext The reader role context. * @param string|null $writerrolename The role name. * @param bool $expectexception Whether the test should throw an exception or not. */ public function test_delete_other_users_events(bool $isadmin, bool $isadminevent, ?\stdClass $writerrolecontext, ?string $writerrolename, bool $expectexception) { global $DB, $USER; $this->resetAfterTest(); $generator = $this->getDataGenerator(); if ($isadminevent) { $this->setAdminUser(); $user = $USER; } else { $user = $generator->create_user(); $this->setUser($user); } $userevent = $this->create_calendar_event('user event', $user->id, 'user', 0, time()); if ($isadmin) { $this->setAdminUser(); } else { $writer = $generator->create_user(); if ($writerrolename && $writerrolecontext) { $managerroleid = $DB->get_field('role', 'id', ['shortname' => $writerrolename]); role_assign($managerroleid, $writer->id, $writerrolecontext->id); } $this->setUser($writer); } if ($expectexception) { $this->expectException(\moodle_exception::class); } $events = [ ['eventid' => $userevent->id, 'repeat' => 0] ]; core_calendar_external::delete_calendar_events($events); } /** * Test the behavior of editing other users' user events * * @dataProvider edit_or_delete_other_users_events_data_provider * @covers \core_calendar_external::submit_create_update_form * @param bool $isadmin Whether the current user is admin. * @param bool $isadminevent Whether it's an admin event or not. * @param stdClass|null $writerrolecontext The reader role context. * @param string|null $writerrolename The role name. * @param bool $expectexception Whether the test should throw an exception or not. */ public function test_edit_other_users_events(bool $isadmin, bool $isadminevent, ?\stdClass $writerrolecontext, ?string $writerrolename, bool $expectexception) { global $DB, $USER; $this->resetAfterTest(); $generator = $this->getDataGenerator(); if ($isadminevent) { $this->setAdminUser(); $user = $USER; } else { $user = $generator->create_user(); } $formdata = [ 'id' => 0, 'userid' => $user->id, 'modulename' => '', 'instance' => 0, 'visible' => 1, 'eventtype' => 'user', 'name' => 'Test', 'timestart' => [ 'day' => 1, 'month' => 1, 'year' => 2021, 'hour' => 1, 'minute' => 0, ], 'description' => [ 'text' => 'xxxxx', 'format' => 1, 'itemid' => 0 ], 'location' => 'Test', 'duration' => 0, ]; $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata); $querystring = http_build_query($formdata, '', '&'); if ($isadmin) { $this->setAdminUser(); } else { $writer = $generator->create_user(); if ($writerrolename && $writerrolecontext) { $managerroleid = $DB->get_field('role', 'id', ['shortname' => $writerrolename]); role_assign($managerroleid, $writer->id, $writerrolecontext->id); } $this->setUser($writer); } $USER->ignoresesskey = true; if ($expectexception) { $this->expectException(\moodle_exception::class); } core_calendar_external::submit_create_update_form($querystring); } /** * A user should not be able load the calendar events for a category they cannot see. */ public function test_get_calendar_events_hidden_category() { global $USER; $this->resetAfterTest(true); $this->setAdminUser(); $generator = $this->getDataGenerator(); $user1 = $generator->create_user(); $category = $generator->create_category(['visible' => 0]); $name = 'Category Event (category: ' . $category->id . ')'; $record = new \stdClass(); $record->categoryid = $category->id; $categoryevent = $this->create_calendar_event($name, $USER->id, 'category', 0, time(), $record); $events = [ 'eventids' => [$categoryevent->id] ]; $options = []; // Admin can load the category event. $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_events_returns(), core_calendar_external::get_calendar_events($events, $options) ); $this->assertEquals($data['events'][0]['id'], $categoryevent->id); // User with no special permission to see hidden categories will not see the event. $this->setUser($user1); $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_events_returns(), core_calendar_external::get_calendar_events($events, $options) ); $this->assertCount(0, $data['events']); $this->assertEquals('nopermissions', $data['warnings'][0]['warningcode']); } /** * Test get_calendar_access_information for admins. */ public function test_get_calendar_access_information_for_admins() { global $CFG; $this->resetAfterTest(true); $this->setAdminUser(); $CFG->calendar_adminseesall = 1; $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_access_information_returns(), core_calendar_external::get_calendar_access_information() ); $this->assertTrue($data['canmanageownentries']); $this->assertTrue($data['canmanagegroupentries']); $this->assertTrue($data['canmanageentries']); } /** * Test get_calendar_access_information for authenticated users. */ public function test_get_calendar_access_information_for_authenticated_users() { $this->resetAfterTest(true); $this->setUser($this->getDataGenerator()->create_user()); $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_access_information_returns(), core_calendar_external::get_calendar_access_information() ); $this->assertTrue($data['canmanageownentries']); $this->assertFalse($data['canmanagegroupentries']); $this->assertFalse($data['canmanageentries']); } /** * Test get_calendar_access_information for student users. */ public function test_get_calendar_access_information_for_student_users() { global $DB; $this->resetAfterTest(true); $user = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $role = $DB->get_record('role', array('shortname' => 'student')); $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id); $this->setUser($user); $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_access_information_returns(), core_calendar_external::get_calendar_access_information($course->id) ); $this->assertTrue($data['canmanageownentries']); $this->assertFalse($data['canmanagegroupentries']); $this->assertFalse($data['canmanageentries']); } /** * Test get_calendar_access_information for teacher users. */ public function test_get_calendar_access_information_for_teacher_users() { global $DB; $this->resetAfterTest(true); $user = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(['groupmode' => 1]); $role = $DB->get_record('role', array('shortname' => 'editingteacher')); $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id); $this->getDataGenerator()->create_group(['courseid' => $course->id]); $this->setUser($user); $data = \external_api::clean_returnvalue( core_calendar_external::get_calendar_access_information_returns(), core_calendar_external::get_calendar_access_information($course->id) ); $this->assertTrue($data['canmanageownentries']); $this->assertTrue($data['canmanagegroupentries']); $this->assertTrue($data['canmanageentries']); } /** * Test get_allowed_event_types for admins. */ public function test_get_allowed_event_types_for_admins() { global $CFG; $this->resetAfterTest(true); $this->setAdminUser(); $CFG->calendar_adminseesall = 1; $data = \external_api::clean_returnvalue( core_calendar_external::get_allowed_event_types_returns(), core_calendar_external::get_allowed_event_types() ); $this->assertEquals(['user', 'site', 'course', 'category'], $data['allowedeventtypes']); } /** * Test get_allowed_event_types for authenticated users. */ public function test_get_allowed_event_types_for_authenticated_users() { $this->resetAfterTest(true); $this->setUser($this->getDataGenerator()->create_user()); $data = \external_api::clean_returnvalue( core_calendar_external::get_allowed_event_types_returns(), core_calendar_external::get_allowed_event_types() ); $this->assertEquals(['user'], $data['allowedeventtypes']); } /** * Test get_allowed_event_types for student users. */ public function test_get_allowed_event_types_for_student_users() { global $DB; $this->resetAfterTest(true); $user = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $role = $DB->get_record('role', array('shortname' => 'student')); $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id); $this->setUser($user); $data = \external_api::clean_returnvalue( core_calendar_external::get_allowed_event_types_returns(), core_calendar_external::get_allowed_event_types($course->id) ); $this->assertEquals(['user'], $data['allowedeventtypes']); } /** * Test get_allowed_event_types for teacher users. */ public function test_get_allowed_event_types_for_teacher_users() { global $DB; $this->resetAfterTest(true); $user = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(['groupmode' => 1]); $role = $DB->get_record('role', array('shortname' => 'editingteacher')); $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id); $this->getDataGenerator()->create_group(['courseid' => $course->id]); $this->setUser($user); $data = \external_api::clean_returnvalue( core_calendar_external::get_allowed_event_types_returns(), core_calendar_external::get_allowed_event_types($course->id) ); $this->assertEquals(['user', 'course', 'group'], $data['allowedeventtypes']); } /** * Test get_timestamps with string keys, with and without optional hour/minute values. */ public function test_get_timestamps_string_keys() { $this->resetAfterTest(true); $this->setAdminUser(); $time1 = new \DateTime('2018-12-30 00:00:00'); $time2 = new \DateTime('2019-03-27 23:59:00'); $dates = [ [ 'key' => 'from', 'year' => $time1->format('Y'), 'month' => $time1->format('m'), 'day' => $time1->format('d'), ], [ 'key' => 'to', 'year' => $time2->format('Y'), 'month' => (int) $time2->format('m'), 'day' => $time2->format('d'), 'hour' => $time2->format('H'), 'minute' => $time2->format('i'), ], ]; $expectedtimestamps = [ 'from' => $time1->getTimestamp(), 'to' => $time2->getTimestamp(), ]; $result = core_calendar_external::get_timestamps($dates); $this->assertEquals(['timestamps'], array_keys($result)); $this->assertEquals(2, count($result['timestamps'])); foreach ($result['timestamps'] as $data) { $this->assertTrue(in_array($data['key'], ['from', 'to'])); $this->assertEquals($expectedtimestamps[$data['key']], $data['timestamp']); } } /** * Test get_timestamps with no keys specified, with and without optional hour/minute values. */ public function test_get_timestamps_no_keys() { $this->resetAfterTest(true); $this->setAdminUser(); $time1 = new \DateTime('2018-12-30 00:00:00'); $time2 = new \DateTime('2019-03-27 23:59:00'); $dates = [ [ 'year' => $time1->format('Y'), 'month' => $time1->format('m'), 'day' => $time1->format('d'), ], [ 'year' => $time2->format('Y'), 'month' => (int) $time2->format('m'), 'day' => $time2->format('d'), 'hour' => $time2->format('H'), 'minute' => $time2->format('i'), ], ]; $expectedtimestamps = [ 0 => $time1->getTimestamp(), 1 => $time2->getTimestamp(), ]; $result = core_calendar_external::get_timestamps($dates); $this->assertEquals(['timestamps'], array_keys($result)); $this->assertEquals(2, count($result['timestamps'])); foreach ($result['timestamps'] as $data) { $this->assertEquals($expectedtimestamps[$data['key']], $data['timestamp']); } } }