1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
17
18/**
19 * External calendar API
20 *
21 * @package    core_calendar
22 * @category   external
23 * @copyright  2012 Ankit Agarwal
24 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 * @since Moodle 2.5
26 */
27
28defined('MOODLE_INTERNAL') || die;
29
30require_once("$CFG->libdir/externallib.php");
31require_once($CFG->dirroot . '/calendar/lib.php');
32
33use \core_calendar\local\api as local_api;
34use \core_calendar\local\event\container as event_container;
35use \core_calendar\local\event\forms\create as create_event_form;
36use \core_calendar\local\event\forms\update as update_event_form;
37use \core_calendar\local\event\mappers\create_update_form_mapper;
38use \core_calendar\external\event_exporter;
39use \core_calendar\external\events_exporter;
40use \core_calendar\external\events_grouped_by_course_exporter;
41use \core_calendar\external\events_related_objects_cache;
42
43/**
44 * Calendar external functions
45 *
46 * @package    core_calendar
47 * @category   external
48 * @copyright  2012 Ankit Agarwal
49 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
50 * @since Moodle 2.5
51 */
52class core_calendar_external extends external_api {
53
54
55    /**
56     * Returns description of method parameters
57     *
58     * @return external_function_parameters
59     * @since Moodle 2.5
60     */
61    public static function delete_calendar_events_parameters() {
62        return new external_function_parameters(
63                array('events' => new external_multiple_structure(
64                        new external_single_structure(
65                                array(
66                                        'eventid' => new external_value(PARAM_INT, 'Event ID', VALUE_REQUIRED, '', NULL_NOT_ALLOWED),
67                                        'repeat'  => new external_value(PARAM_BOOL, 'Delete comeplete series if repeated event')
68                                ), 'List of events to delete'
69                        )
70                    )
71                )
72        );
73    }
74
75    /**
76     * Delete Calendar events
77     *
78     * @param array $eventids A list of event ids with repeat flag to delete
79     * @return null
80     * @since Moodle 2.5
81     */
82    public static function delete_calendar_events($events) {
83        global $DB;
84
85        // Parameter validation.
86        $params = self::validate_parameters(self:: delete_calendar_events_parameters(), array('events' => $events));
87
88        $transaction = $DB->start_delegated_transaction();
89
90        foreach ($params['events'] as $event) {
91            $eventobj = calendar_event::load($event['eventid']);
92
93            // Let's check if the user is allowed to delete an event.
94            if (!calendar_delete_event_allowed($eventobj)) {
95                throw new moodle_exception('nopermissions', 'error', '', get_string('deleteevent', 'calendar'));
96            }
97            // Time to do the magic.
98            $eventobj->delete($event['repeat']);
99        }
100
101        // Everything done smoothly, let's commit.
102        $transaction->allow_commit();
103
104        return null;
105    }
106
107    /**
108     * Returns description of method result value
109     *
110     * @return external_description
111     * @since Moodle 2.5
112     */
113    public static function  delete_calendar_events_returns() {
114        return null;
115    }
116
117    /**
118     * Returns description of method parameters
119     *
120     * @return external_function_parameters
121     * @since Moodle 2.5
122     */
123    public static function get_calendar_events_parameters() {
124        return new external_function_parameters(
125                array('events' => new external_single_structure(
126                            array(
127                                    'eventids' => new external_multiple_structure(
128                                            new external_value(PARAM_INT, 'event ids')
129                                            , 'List of event ids',
130                                            VALUE_DEFAULT, array()),
131                                    'courseids' => new external_multiple_structure(
132                                            new external_value(PARAM_INT, 'course ids')
133                                            , 'List of course ids for which events will be returned',
134                                            VALUE_DEFAULT, array()),
135                                    'groupids' => new external_multiple_structure(
136                                            new external_value(PARAM_INT, 'group ids')
137                                            , 'List of group ids for which events should be returned',
138                                            VALUE_DEFAULT, array()),
139                                    'categoryids' => new external_multiple_structure(
140                                            new external_value(PARAM_INT, 'Category ids'),
141                                            'List of category ids for which events will be returned',
142                                            VALUE_DEFAULT, array()),
143                            ), 'Event details', VALUE_DEFAULT, array()),
144                    'options' => new external_single_structure(
145                            array(
146                                    'userevents' => new external_value(PARAM_BOOL,
147                                             "Set to true to return current user's user events",
148                                             VALUE_DEFAULT, true, NULL_ALLOWED),
149                                    'siteevents' => new external_value(PARAM_BOOL,
150                                             "Set to true to return site events",
151                                             VALUE_DEFAULT, true, NULL_ALLOWED),
152                                    'timestart' => new external_value(PARAM_INT,
153                                             "Time from which events should be returned",
154                                             VALUE_DEFAULT, 0, NULL_ALLOWED),
155                                    'timeend' => new external_value(PARAM_INT,
156                                             "Time to which the events should be returned. We treat 0 and null as no end",
157                                             VALUE_DEFAULT, 0, NULL_ALLOWED),
158                                    'ignorehidden' => new external_value(PARAM_BOOL,
159                                             "Ignore hidden events or not",
160                                             VALUE_DEFAULT, true, NULL_ALLOWED),
161
162                            ), 'Options', VALUE_DEFAULT, array())
163                )
164        );
165    }
166
167    /**
168     * Get Calendar events
169     *
170     * @param array $events A list of events
171     * @param array $options various options
172     * @return array Array of event details
173     * @since Moodle 2.5
174     */
175    public static function get_calendar_events($events = array(), $options = array()) {
176        global $SITE, $DB, $USER;
177
178        // Parameter validation.
179        $params = self::validate_parameters(self::get_calendar_events_parameters(), array('events' => $events, 'options' => $options));
180        $funcparam = array('courses' => array(), 'groups' => array(), 'categories' => array());
181        $hassystemcap = has_capability('moodle/calendar:manageentries', context_system::instance());
182        $warnings = array();
183        $coursecategories = array();
184
185        // Let us find out courses and their categories that we can return events from.
186        if (!$hassystemcap) {
187            $courseobjs = enrol_get_my_courses();
188            $courses = array_keys($courseobjs);
189
190            $coursecategories = array_flip(array_map(function($course) {
191                return $course->category;
192            }, $courseobjs));
193
194            foreach ($params['events']['courseids'] as $id) {
195               try {
196                    $context = context_course::instance($id);
197                    self::validate_context($context);
198                    $funcparam['courses'][] = $id;
199                } catch (Exception $e) {
200                    $warnings[] = array(
201                        'item' => 'course',
202                        'itemid' => $id,
203                        'warningcode' => 'nopermissions',
204                        'message' => 'No access rights in course context '.$e->getMessage().$e->getTraceAsString()
205                    );
206                }
207            }
208        } else {
209            $courses = $params['events']['courseids'];
210            $funcparam['courses'] = $courses;
211
212            if (!empty($courses)) {
213                list($wheresql, $sqlparams) = $DB->get_in_or_equal($courses);
214                $wheresql = "id $wheresql";
215                $coursecategories = array_flip(array_map(function($course) {
216                    return $course->category;
217                }, $DB->get_records_select('course', $wheresql, $sqlparams, '', 'category')));
218            }
219        }
220
221        // Let us findout groups that we can return events from.
222        if (!$hassystemcap) {
223            $groups = groups_get_my_groups();
224            $groups = array_keys($groups);
225            foreach ($params['events']['groupids'] as $id) {
226                if (in_array($id, $groups)) {
227                    $funcparam['groups'][] = $id;
228                } else {
229                    $warnings[] = array('item' => $id, 'warningcode' => 'nopermissions', 'message' => 'you do not have permissions to access this group');
230                }
231            }
232        } else {
233            $groups = $params['events']['groupids'];
234            $funcparam['groups'] = $groups;
235        }
236
237        $categories = array();
238        if ($hassystemcap || !empty($courses)) {
239            // Use the category id as the key in the following array. That way we do not have to remove duplicates and
240            // have a faster lookup later.
241            $categories = [];
242
243            if (!empty($params['events']['categoryids'])) {
244                $catobjs = \core_course_category::get_many(
245                    array_merge($params['events']['categoryids'], array_keys($coursecategories)));
246                foreach ($catobjs as $catobj) {
247                    if (isset($coursecategories[$catobj->id]) ||
248                            has_capability('moodle/category:manage', $catobj->get_context())) {
249                        // If the user has access to a course in this category or can manage the category,
250                        // then they can see all parent categories too.
251                        $categories[$catobj->id] = true;
252                        foreach ($catobj->get_parents() as $catid) {
253                            $categories[$catid] = true;
254                        }
255                    }
256                }
257                $funcparam['categories'] = array_keys($categories);
258            } else {
259                // Fetch all categories where this user has any enrolment, and all categories that this user can manage.
260                $calcatcache = cache::make('core', 'calendar_categories');
261                // Do not use cache if the user has the system capability as $coursecategories might not represent the
262                // courses the user is enrolled in.
263                $categories = (!$hassystemcap) ? $calcatcache->get('site') : false;
264                if ($categories !== false) {
265                    // The ids are stored in a list in the cache.
266                    $funcparam['categories'] = $categories;
267                    $categories = array_flip($categories);
268                } else {
269                    $categories = [];
270                    foreach (\core_course_category::get_all() as $category) {
271                        if (isset($coursecategories[$category->id]) ||
272                                has_capability('moodle/category:manage', $category->get_context(), $USER, false)) {
273                            // If the user has access to a course in this category or can manage the category,
274                            // then they can see all parent categories too.
275                            $categories[$category->id] = true;
276                            foreach ($category->get_parents() as $catid) {
277                                $categories[$catid] = true;
278                            }
279                        }
280                    }
281                    $funcparam['categories'] = array_keys($categories);
282                    if (!$hassystemcap) {
283                        $calcatcache->set('site', $funcparam['categories']);
284                    }
285                }
286            }
287        }
288
289        // Do we need user events?
290        if (!empty($params['options']['userevents'])) {
291            $funcparam['users'] = array($USER->id);
292        } else {
293            $funcparam['users'] = false;
294        }
295
296        // Do we need site events?
297        if (!empty($params['options']['siteevents'])) {
298            $funcparam['courses'][] = $SITE->id;
299        }
300
301        // We treat 0 and null as no end.
302        if (empty($params['options']['timeend'])) {
303            $params['options']['timeend'] = PHP_INT_MAX;
304        }
305
306        // Event list does not check visibility and permissions, we'll check that later.
307        $eventlist = calendar_get_legacy_events($params['options']['timestart'], $params['options']['timeend'],
308                $funcparam['users'], $funcparam['groups'], $funcparam['courses'], true,
309                $params['options']['ignorehidden'], $funcparam['categories']);
310
311        // WS expects arrays.
312        $events = array();
313
314        // We need to get events asked for eventids.
315        if ($eventsbyid = calendar_get_events_by_id($params['events']['eventids'])) {
316            $eventlist += $eventsbyid;
317        }
318        foreach ($eventlist as $eventid => $eventobj) {
319            $event = (array) $eventobj;
320            // Description formatting.
321            $calendareventobj = new calendar_event($event);
322            $event['name'] = $calendareventobj->format_external_name();
323            list($event['description'], $event['format']) = $calendareventobj->format_external_text();
324
325            if ($hassystemcap) {
326                // User can see everything, no further check is needed.
327                $events[$eventid] = $event;
328            } else if (!empty($eventobj->modulename)) {
329                $courseid = $eventobj->courseid;
330                if (!$courseid) {
331                    if (!$calendareventobj->context || !($context = $calendareventobj->context->get_course_context(false))) {
332                        continue;
333                    }
334                    $courseid = $context->instanceid;
335                }
336                $instances = get_fast_modinfo($courseid)->get_instances_of($eventobj->modulename);
337                if (!empty($instances[$eventobj->instance]->uservisible)) {
338                    $events[$eventid] = $event;
339                }
340            } else {
341                // Can the user actually see this event?
342                $eventobj = calendar_event::load($eventobj);
343                if ((($eventobj->courseid == $SITE->id) && (empty($eventobj->categoryid))) ||
344                            (!empty($eventobj->categoryid) && isset($categories[$eventobj->categoryid])) ||
345                            (!empty($eventobj->groupid) && in_array($eventobj->groupid, $groups)) ||
346                            (!empty($eventobj->courseid) && in_array($eventobj->courseid, $courses)) ||
347                            ($USER->id == $eventobj->userid) ||
348                            (calendar_edit_event_allowed($eventobj))) {
349                    $events[$eventid] = $event;
350                } else {
351                    $warnings[] = array('item' => $eventid, 'warningcode' => 'nopermissions', 'message' => 'you do not have permissions to view this event');
352                }
353            }
354        }
355        return array('events' => $events, 'warnings' => $warnings);
356    }
357
358    /**
359     * Returns description of method result value
360     *
361     * @return external_description
362     * @since Moodle 2.5
363     */
364    public static function  get_calendar_events_returns() {
365        return new external_single_structure(array(
366                'events' => new external_multiple_structure( new external_single_structure(
367                        array(
368                            'id' => new external_value(PARAM_INT, 'event id'),
369                            'name' => new external_value(PARAM_RAW, 'event name'),
370                            'description' => new external_value(PARAM_RAW, 'Description', VALUE_OPTIONAL, null, NULL_ALLOWED),
371                            'format' => new external_format_value('description'),
372                            'courseid' => new external_value(PARAM_INT, 'course id'),
373                            'categoryid' => new external_value(PARAM_INT, 'Category id (only for category events).',
374                                VALUE_OPTIONAL),
375                            'groupid' => new external_value(PARAM_INT, 'group id'),
376                            'userid' => new external_value(PARAM_INT, 'user id'),
377                            'repeatid' => new external_value(PARAM_INT, 'repeat id'),
378                            'modulename' => new external_value(PARAM_TEXT, 'module name', VALUE_OPTIONAL, null, NULL_ALLOWED),
379                            'instance' => new external_value(PARAM_INT, 'instance id'),
380                            'eventtype' => new external_value(PARAM_TEXT, 'Event type'),
381                            'timestart' => new external_value(PARAM_INT, 'timestart'),
382                            'timeduration' => new external_value(PARAM_INT, 'time duration'),
383                            'visible' => new external_value(PARAM_INT, 'visible'),
384                            'uuid' => new external_value(PARAM_TEXT, 'unique id of ical events', VALUE_OPTIONAL, null, NULL_NOT_ALLOWED),
385                            'sequence' => new external_value(PARAM_INT, 'sequence'),
386                            'timemodified' => new external_value(PARAM_INT, 'time modified'),
387                            'subscriptionid' => new external_value(PARAM_INT, 'Subscription id', VALUE_OPTIONAL, null, NULL_ALLOWED),
388                        ), 'event')
389                 ),
390                 'warnings' => new external_warnings()
391                )
392        );
393    }
394
395    /**
396     * Returns description of method parameters.
397     *
398     * @since Moodle 3.3
399     * @return external_function_parameters
400     */
401    public static function get_calendar_action_events_by_timesort_parameters() {
402        return new external_function_parameters(
403            array(
404                'timesortfrom' => new external_value(PARAM_INT, 'Time sort from', VALUE_DEFAULT, 0),
405                'timesortto' => new external_value(PARAM_INT, 'Time sort to', VALUE_DEFAULT, null),
406                'aftereventid' => new external_value(PARAM_INT, 'The last seen event id', VALUE_DEFAULT, 0),
407                'limitnum' => new external_value(PARAM_INT, 'Limit number', VALUE_DEFAULT, 20),
408                'limittononsuspendedevents' => new external_value(PARAM_BOOL,
409                        'Limit the events to courses the user is not suspended in', VALUE_DEFAULT, false),
410                'userid' => new external_value(PARAM_INT, 'The user id', VALUE_DEFAULT, null),
411            )
412        );
413    }
414
415    /**
416     * Get calendar action events based on the timesort value.
417     *
418     * @since Moodle 3.3
419     * @param null|int $timesortfrom Events after this time (inclusive)
420     * @param null|int $timesortto Events before this time (inclusive)
421     * @param null|int $aftereventid Get events with ids greater than this one
422     * @param int $limitnum Limit the number of results to this value
423     * @param null|int $userid The user id
424     * @return array
425     */
426    public static function get_calendar_action_events_by_timesort($timesortfrom = 0, $timesortto = null,
427                                                       $aftereventid = 0, $limitnum = 20, $limittononsuspendedevents = false,
428                                                       $userid = null) {
429        global $PAGE, $USER;
430
431        $params = self::validate_parameters(
432            self::get_calendar_action_events_by_timesort_parameters(),
433            [
434                'timesortfrom' => $timesortfrom,
435                'timesortto' => $timesortto,
436                'aftereventid' => $aftereventid,
437                'limitnum' => $limitnum,
438                'limittononsuspendedevents' => $limittononsuspendedevents,
439                'userid' => $userid,
440            ]
441        );
442        if ($params['userid']) {
443            $user = \core_user::get_user($params['userid']);
444        } else {
445            $user = $USER;
446        }
447
448        $context = \context_user::instance($user->id);
449        self::validate_context($context);
450
451        if ($params['userid'] && $USER->id !== $params['userid'] && !has_capability('moodle/calendar:manageentries', $context)) {
452            throw new \required_capability_exception($context, 'moodle/calendar:manageentries', 'nopermission', '');
453        }
454
455        if (empty($params['aftereventid'])) {
456            $params['aftereventid'] = null;
457        }
458
459        $renderer = $PAGE->get_renderer('core_calendar');
460        $events = local_api::get_action_events_by_timesort(
461            $params['timesortfrom'],
462            $params['timesortto'],
463            $params['aftereventid'],
464            $params['limitnum'],
465            $params['limittononsuspendedevents'],
466            $user
467        );
468
469        $exportercache = new events_related_objects_cache($events);
470        $exporter = new events_exporter($events, ['cache' => $exportercache]);
471
472        return $exporter->export($renderer);
473    }
474
475    /**
476     * Returns description of method result value.
477     *
478     * @since Moodle 3.3
479     * @return external_description
480     */
481    public static function get_calendar_action_events_by_timesort_returns() {
482        return events_exporter::get_read_structure();
483    }
484
485    /**
486     * Returns description of method parameters.
487     *
488     * @return external_function_parameters
489     */
490    public static function get_calendar_action_events_by_course_parameters() {
491        return new external_function_parameters(
492            array(
493                'courseid' => new external_value(PARAM_INT, 'Course id'),
494                'timesortfrom' => new external_value(PARAM_INT, 'Time sort from', VALUE_DEFAULT, null),
495                'timesortto' => new external_value(PARAM_INT, 'Time sort to', VALUE_DEFAULT, null),
496                'aftereventid' => new external_value(PARAM_INT, 'The last seen event id', VALUE_DEFAULT, 0),
497                'limitnum' => new external_value(PARAM_INT, 'Limit number', VALUE_DEFAULT, 20)
498            )
499        );
500    }
501
502    /**
503     * Get calendar action events for the given course.
504     *
505     * @since Moodle 3.3
506     * @param int $courseid Only events in this course
507     * @param null|int $timesortfrom Events after this time (inclusive)
508     * @param null|int $timesortto Events before this time (inclusive)
509     * @param null|int $aftereventid Get events with ids greater than this one
510     * @param int $limitnum Limit the number of results to this value
511     * @return array
512     */
513    public static function get_calendar_action_events_by_course(
514        $courseid, $timesortfrom = null, $timesortto = null, $aftereventid = 0, $limitnum = 20) {
515
516        global $PAGE, $USER;
517
518        $user = null;
519        $params = self::validate_parameters(
520            self::get_calendar_action_events_by_course_parameters(),
521            [
522                'courseid' => $courseid,
523                'timesortfrom' => $timesortfrom,
524                'timesortto' => $timesortto,
525                'aftereventid' => $aftereventid,
526                'limitnum' => $limitnum,
527            ]
528        );
529        $context = \context_user::instance($USER->id);
530        self::validate_context($context);
531
532        if (empty($params['aftereventid'])) {
533            $params['aftereventid'] = null;
534        }
535
536        $courses = enrol_get_my_courses('*', null, 0, [$courseid]);
537        $courses = array_values($courses);
538
539        if (empty($courses)) {
540            return [];
541        }
542
543        $course = $courses[0];
544        $renderer = $PAGE->get_renderer('core_calendar');
545        $events = local_api::get_action_events_by_course(
546            $course,
547            $params['timesortfrom'],
548            $params['timesortto'],
549            $params['aftereventid'],
550            $params['limitnum']
551        );
552
553        $exportercache = new events_related_objects_cache($events, $courses);
554        $exporter = new events_exporter($events, ['cache' => $exportercache]);
555
556        return $exporter->export($renderer);
557    }
558
559    /**
560     * Returns description of method result value.
561     *
562     * @return external_description
563     */
564    public static function get_calendar_action_events_by_course_returns() {
565        return events_exporter::get_read_structure();
566    }
567
568    /**
569     * Returns description of method parameters.
570     *
571     * @return external_function_parameters
572     */
573    public static function get_calendar_action_events_by_courses_parameters() {
574        return new external_function_parameters(
575            array(
576                'courseids' => new external_multiple_structure(
577                    new external_value(PARAM_INT, 'Course id')
578                ),
579                'timesortfrom' => new external_value(PARAM_INT, 'Time sort from', VALUE_DEFAULT, null),
580                'timesortto' => new external_value(PARAM_INT, 'Time sort to', VALUE_DEFAULT, null),
581                'limitnum' => new external_value(PARAM_INT, 'Limit number', VALUE_DEFAULT, 10)
582            )
583        );
584    }
585
586    /**
587     * Get calendar action events for a given list of courses.
588     *
589     * @since Moodle 3.3
590     * @param array $courseids Only include events for these courses
591     * @param null|int $timesortfrom Events after this time (inclusive)
592     * @param null|int $timesortto Events before this time (inclusive)
593     * @param int $limitnum Limit the number of results per course to this value
594     * @return array
595     */
596    public static function get_calendar_action_events_by_courses(
597        array $courseids, $timesortfrom = null, $timesortto = null, $limitnum = 10) {
598
599        global $PAGE, $USER;
600
601        $user = null;
602        $params = self::validate_parameters(
603            self::get_calendar_action_events_by_courses_parameters(),
604            [
605                'courseids' => $courseids,
606                'timesortfrom' => $timesortfrom,
607                'timesortto' => $timesortto,
608                'limitnum' => $limitnum,
609            ]
610        );
611        $context = \context_user::instance($USER->id);
612        self::validate_context($context);
613
614        if (empty($params['courseids'])) {
615            return ['groupedbycourse' => []];
616        }
617
618        $renderer = $PAGE->get_renderer('core_calendar');
619        $courses = enrol_get_my_courses('*', null, 0, $params['courseids']);
620        $courses = array_values($courses);
621
622        if (empty($courses)) {
623            return ['groupedbycourse' => []];
624        }
625
626        $events = local_api::get_action_events_by_courses(
627            $courses,
628            $params['timesortfrom'],
629            $params['timesortto'],
630            $params['limitnum']
631        );
632
633        if (empty($events)) {
634            return ['groupedbycourse' => []];
635        }
636
637        $exportercache = new events_related_objects_cache($events, $courses);
638        $exporter = new events_grouped_by_course_exporter($events, ['cache' => $exportercache]);
639
640        return $exporter->export($renderer);
641    }
642
643    /**
644     * Returns description of method result value.
645     *
646     * @return external_description
647     */
648    public static function get_calendar_action_events_by_courses_returns() {
649        return events_grouped_by_course_exporter::get_read_structure();
650    }
651
652    /**
653     * Returns description of method parameters.
654     *
655     * @return external_function_parameters.
656     * @since Moodle 2.5
657     */
658    public static function create_calendar_events_parameters() {
659        // Userid is always current user, so no need to get it from client.
660        // Module based calendar events are not allowed here. Hence no need of instance and modulename.
661        // subscription id and uuid is not allowed as this is not an ical api.
662        return new external_function_parameters(
663                array('events' => new external_multiple_structure(
664                        new external_single_structure(
665                            array(
666                                'name' => new external_value(PARAM_TEXT, 'event name', VALUE_REQUIRED, '', NULL_NOT_ALLOWED),
667                                'description' => new external_value(PARAM_RAW, 'Description', VALUE_DEFAULT, null, NULL_ALLOWED),
668                                'format' => new external_format_value('description', VALUE_DEFAULT),
669                                'courseid' => new external_value(PARAM_INT, 'course id', VALUE_DEFAULT, 0, NULL_NOT_ALLOWED),
670                                'groupid' => new external_value(PARAM_INT, 'group id', VALUE_DEFAULT, 0, NULL_NOT_ALLOWED),
671                                'repeats' => new external_value(PARAM_INT, 'number of repeats', VALUE_DEFAULT, 0, NULL_NOT_ALLOWED),
672                                'eventtype' => new external_value(PARAM_TEXT, 'Event type', VALUE_DEFAULT, 'user', NULL_NOT_ALLOWED),
673                                'timestart' => new external_value(PARAM_INT, 'timestart', VALUE_DEFAULT, time(), NULL_NOT_ALLOWED),
674                                'timeduration' => new external_value(PARAM_INT, 'time duration', VALUE_DEFAULT, 0, NULL_NOT_ALLOWED),
675                                'visible' => new external_value(PARAM_INT, 'visible', VALUE_DEFAULT, 1, NULL_NOT_ALLOWED),
676                                'sequence' => new external_value(PARAM_INT, 'sequence', VALUE_DEFAULT, 1, NULL_NOT_ALLOWED),
677                            ), 'event')
678                )
679            )
680        );
681    }
682
683    /**
684     * Create calendar events.
685     *
686     * @param array $events A list of events to create.
687     * @return array array of events created.
688     * @since Moodle 2.5
689     * @throws moodle_exception if user doesnt have the permission to create events.
690     */
691    public static function create_calendar_events($events) {
692        global $DB, $USER;
693
694        // Parameter validation.
695        $params = self::validate_parameters(self::create_calendar_events_parameters(), array('events' => $events));
696
697        $transaction = $DB->start_delegated_transaction();
698        $return = array();
699        $warnings = array();
700
701        foreach ($params['events'] as $event) {
702
703            // Let us set some defaults.
704            $event['userid'] = $USER->id;
705            $event['modulename'] = '';
706            $event['instance'] = 0;
707            $event['subscriptionid'] = null;
708            $event['uuid']= '';
709            $event['format'] = external_validate_format($event['format']);
710            if ($event['repeats'] > 0) {
711                $event['repeat'] = 1;
712            } else {
713                $event['repeat'] = 0;
714            }
715
716            $eventobj = new calendar_event($event);
717
718            // Let's check if the user is allowed to delete an event.
719            if (!calendar_add_event_allowed($eventobj)) {
720                $warnings [] = array('item' => $event['name'], 'warningcode' => 'nopermissions', 'message' => 'you do not have permissions to create this event');
721                continue;
722            }
723            // Let's create the event.
724            $var = $eventobj->create($event);
725            $var = (array)$var->properties();
726            if ($event['repeat']) {
727                $children = $DB->get_records('event', array('repeatid' => $var['id']));
728                foreach ($children as $child) {
729                    $return[] = (array) $child;
730                }
731            } else {
732                $return[] = $var;
733            }
734        }
735
736        // Everything done smoothly, let's commit.
737        $transaction->allow_commit();
738        return array('events' => $return, 'warnings' => $warnings);
739    }
740
741    /**
742     * Returns description of method result value.
743     *
744     * @return external_description.
745     * @since Moodle 2.5
746     */
747    public static function  create_calendar_events_returns() {
748            return new external_single_structure(
749                    array(
750                        'events' => new external_multiple_structure( new external_single_structure(
751                                array(
752                                    'id' => new external_value(PARAM_INT, 'event id'),
753                                    'name' => new external_value(PARAM_RAW, 'event name'),
754                                    'description' => new external_value(PARAM_RAW, 'Description', VALUE_OPTIONAL),
755                                    'format' => new external_format_value('description'),
756                                    'courseid' => new external_value(PARAM_INT, 'course id'),
757                                    'groupid' => new external_value(PARAM_INT, 'group id'),
758                                    'userid' => new external_value(PARAM_INT, 'user id'),
759                                    'repeatid' => new external_value(PARAM_INT, 'repeat id', VALUE_OPTIONAL),
760                                    'modulename' => new external_value(PARAM_TEXT, 'module name', VALUE_OPTIONAL),
761                                    'instance' => new external_value(PARAM_INT, 'instance id'),
762                                    'eventtype' => new external_value(PARAM_TEXT, 'Event type'),
763                                    'timestart' => new external_value(PARAM_INT, 'timestart'),
764                                    'timeduration' => new external_value(PARAM_INT, 'time duration'),
765                                    'visible' => new external_value(PARAM_INT, 'visible'),
766                                    'uuid' => new external_value(PARAM_TEXT, 'unique id of ical events', VALUE_OPTIONAL, '', NULL_NOT_ALLOWED),
767                                    'sequence' => new external_value(PARAM_INT, 'sequence'),
768                                    'timemodified' => new external_value(PARAM_INT, 'time modified'),
769                                    'subscriptionid' => new external_value(PARAM_INT, 'Subscription id', VALUE_OPTIONAL),
770                                ), 'event')
771                        ),
772                      'warnings' => new external_warnings()
773                    )
774            );
775    }
776
777    /**
778     * Returns description of method parameters.
779     *
780     * @return external_function_parameters
781     */
782    public static function get_calendar_event_by_id_parameters() {
783        return new external_function_parameters(
784            array(
785                'eventid' => new external_value(PARAM_INT, 'The event id to be retrieved'),
786            )
787        );
788    }
789
790    /**
791     * Get calendar event by id.
792     *
793     * @param int $eventid The calendar event id to be retrieved.
794     * @return array Array of event details
795     */
796    public static function get_calendar_event_by_id($eventid) {
797        global $PAGE, $USER;
798
799        $params = self::validate_parameters(self::get_calendar_event_by_id_parameters(), ['eventid' => $eventid]);
800        $context = \context_user::instance($USER->id);
801
802        self::validate_context($context);
803        $warnings = array();
804
805        $eventvault = event_container::get_event_vault();
806        if ($event = $eventvault->get_event_by_id($eventid)) {
807            $mapper = event_container::get_event_mapper();
808            if (!calendar_view_event_allowed($mapper->from_event_to_legacy_event($event))) {
809                $event = null;
810            }
811        }
812
813        if (!$event) {
814            // We can't return a warning in this case because the event is not optional.
815            // We don't know the context for the event and it's not worth loading it.
816            $syscontext = context_system::instance();
817            throw new \required_capability_exception($syscontext, 'moodle/course:view', 'nopermission', '');
818        }
819
820        $cache = new events_related_objects_cache([$event]);
821        $relatedobjects = [
822            'context' => $cache->get_context($event),
823            'course' => $cache->get_course($event),
824        ];
825
826        $exporter = new event_exporter($event, $relatedobjects);
827        $renderer = $PAGE->get_renderer('core_calendar');
828
829        return array('event' => $exporter->export($renderer), 'warnings' => $warnings);
830    }
831
832    /**
833     * Returns description of method result value
834     *
835     * @return external_description
836     */
837    public static function get_calendar_event_by_id_returns() {
838        $eventstructure = event_exporter::get_read_structure();
839
840        return new external_single_structure(array(
841            'event' => $eventstructure,
842            'warnings' => new external_warnings()
843            )
844        );
845    }
846
847    /**
848     * Returns description of method parameters.
849     *
850     * @return external_function_parameters.
851     */
852    public static function submit_create_update_form_parameters() {
853        return new external_function_parameters(
854            [
855                'formdata' => new external_value(PARAM_RAW, 'The data from the event form'),
856            ]
857        );
858    }
859
860    /**
861     * Handles the event form submission.
862     *
863     * @param string $formdata The event form data in a URI encoded param string
864     * @return array The created or modified event
865     * @throws moodle_exception
866     */
867    public static function submit_create_update_form($formdata) {
868        global $USER, $PAGE, $CFG;
869        require_once($CFG->libdir."/filelib.php");
870
871        // Parameter validation.
872        $params = self::validate_parameters(self::submit_create_update_form_parameters(), ['formdata' => $formdata]);
873        $context = \context_user::instance($USER->id);
874        $data = [];
875
876        self::validate_context($context);
877        parse_str($params['formdata'], $data);
878
879        if (WS_SERVER) {
880            // Request via WS, ignore sesskey checks in form library.
881            $USER->ignoresesskey = true;
882        }
883
884        $eventtype = isset($data['eventtype']) ? $data['eventtype'] : null;
885        $coursekey = ($eventtype == 'group') ? 'groupcourseid' : 'courseid';
886        $courseid = (!empty($data[$coursekey])) ? $data[$coursekey] : null;
887        $editoroptions = \core_calendar\local\event\forms\create::build_editor_options($context);
888        $formoptions = ['editoroptions' => $editoroptions, 'courseid' => $courseid];
889        $formoptions['eventtypes'] = calendar_get_allowed_event_types($courseid);
890        if ($courseid) {
891            require_once($CFG->libdir . '/grouplib.php');
892            $groupcoursedata = groups_get_course_data($courseid);
893            if (!empty($groupcoursedata->groups)) {
894                $formoptions['groups'] = [];
895                foreach ($groupcoursedata->groups as $groupid => $groupdata) {
896                    $formoptions['groups'][$groupid] = $groupdata->name;
897                }
898            }
899        }
900
901        if (!empty($data['id'])) {
902            $eventid = clean_param($data['id'], PARAM_INT);
903            $legacyevent = calendar_event::load($eventid);
904            $legacyevent->count_repeats();
905            $formoptions['event'] = $legacyevent;
906            $mform = new update_event_form(null, $formoptions, 'post', '', null, true, $data);
907        } else {
908            $legacyevent = null;
909            $mform = new create_event_form(null, $formoptions, 'post', '', null, true, $data);
910        }
911
912        if ($validateddata = $mform->get_data()) {
913            $formmapper = new create_update_form_mapper();
914            $properties = $formmapper->from_data_to_event_properties($validateddata);
915
916            if (is_null($legacyevent)) {
917                $legacyevent = new \calendar_event($properties);
918                // Need to do this in order to initialise the description
919                // property which then triggers the update function below
920                // to set the appropriate default properties on the event.
921                $properties = $legacyevent->properties(true);
922            }
923
924            if (!calendar_edit_event_allowed($legacyevent, true)) {
925                print_error('nopermissiontoupdatecalendar');
926            }
927
928            $legacyevent->update($properties);
929            $eventcontext = $legacyevent->context;
930
931            file_remove_editor_orphaned_files($validateddata->description);
932
933            // Take any files added to the description draft file area and
934            // convert them into the proper event description file area. Also
935            // parse the description text and replace the URLs to the draft files
936            // with the @@PLUGIN_FILE@@ placeholder to be persisted in the DB.
937            $description = file_save_draft_area_files(
938                $validateddata->description['itemid'],
939                $eventcontext->id,
940                'calendar',
941                'event_description',
942                $legacyevent->id,
943                create_event_form::build_editor_options($eventcontext),
944                $validateddata->description['text']
945            );
946
947            // If draft files were found then we need to save the new
948            // description value.
949            if ($description != $validateddata->description['text']) {
950                $properties->id = $legacyevent->id;
951                $properties->description = $description;
952                $legacyevent->update($properties);
953            }
954
955            $eventmapper = event_container::get_event_mapper();
956            $event = $eventmapper->from_legacy_event_to_event($legacyevent);
957            $cache = new events_related_objects_cache([$event]);
958            $relatedobjects = [
959                'context' => $cache->get_context($event),
960                'course' => $cache->get_course($event),
961            ];
962            $exporter = new event_exporter($event, $relatedobjects);
963            $renderer = $PAGE->get_renderer('core_calendar');
964
965            return [ 'event' => $exporter->export($renderer) ];
966        } else {
967            return [ 'validationerror' => true ];
968        }
969    }
970
971    /**
972     * Returns description of method result value.
973     *
974     * @return external_description.
975     */
976    public static function  submit_create_update_form_returns() {
977        $eventstructure = event_exporter::get_read_structure();
978        $eventstructure->required = VALUE_OPTIONAL;
979
980        return new external_single_structure(
981            array(
982                'event' => $eventstructure,
983                'validationerror' => new external_value(PARAM_BOOL, 'Invalid form data', VALUE_DEFAULT, false),
984            )
985        );
986    }
987
988    /**
989     * Get data for the monthly calendar view.
990     *
991     * @param   int     $year The year to be shown
992     * @param   int     $month The month to be shown
993     * @param   int     $courseid The course to be included
994     * @param   int     $categoryid The category to be included
995     * @param   bool    $includenavigation Whether to include navigation
996     * @param   bool    $mini Whether to return the mini month view or not
997     * @param   int     $day The day we want to keep as the current day
998     * @return  array
999     */
1000    public static function get_calendar_monthly_view($year, $month, $courseid, $categoryid, $includenavigation, $mini, $day) {
1001        global $USER, $PAGE;
1002
1003        // Parameter validation.
1004        $params = self::validate_parameters(self::get_calendar_monthly_view_parameters(), [
1005            'year' => $year,
1006            'month' => $month,
1007            'courseid' => $courseid,
1008            'categoryid' => $categoryid,
1009            'includenavigation' => $includenavigation,
1010            'mini' => $mini,
1011            'day' => $day,
1012        ]);
1013
1014        $context = \context_user::instance($USER->id);
1015        self::validate_context($context);
1016        $PAGE->set_url('/calendar/');
1017
1018        $type = \core_calendar\type_factory::get_calendar_instance();
1019
1020        $time = $type->convert_to_timestamp($params['year'], $params['month'], $params['day']);
1021        $calendar = \calendar_information::create($time, $params['courseid'], $params['categoryid']);
1022        self::validate_context($calendar->context);
1023
1024        $view = $params['mini'] ? 'mini' : 'month';
1025        list($data, $template) = calendar_get_view($calendar, $view, $params['includenavigation']);
1026
1027        return $data;
1028    }
1029
1030    /**
1031     * Returns description of method parameters.
1032     *
1033     * @return external_function_parameters
1034     */
1035    public static function get_calendar_monthly_view_parameters() {
1036        return new external_function_parameters(
1037            [
1038                'year' => new external_value(PARAM_INT, 'Year to be viewed', VALUE_REQUIRED),
1039                'month' => new external_value(PARAM_INT, 'Month to be viewed', VALUE_REQUIRED),
1040                'courseid' => new external_value(PARAM_INT, 'Course being viewed', VALUE_DEFAULT, SITEID, NULL_ALLOWED),
1041                'categoryid' => new external_value(PARAM_INT, 'Category being viewed', VALUE_DEFAULT, null, NULL_ALLOWED),
1042                'includenavigation' => new external_value(
1043                    PARAM_BOOL,
1044                    'Whether to show course navigation',
1045                    VALUE_DEFAULT,
1046                    true,
1047                    NULL_ALLOWED
1048                ),
1049                'mini' => new external_value(
1050                    PARAM_BOOL,
1051                    'Whether to return the mini month view or not',
1052                    VALUE_DEFAULT,
1053                    false,
1054                    NULL_ALLOWED
1055                ),
1056                'day' => new external_value(PARAM_INT, 'Day to be viewed', VALUE_DEFAULT, 1),
1057            ]
1058        );
1059    }
1060
1061    /**
1062     * Returns description of method result value.
1063     *
1064     * @return external_description
1065     */
1066    public static function get_calendar_monthly_view_returns() {
1067        return \core_calendar\external\month_exporter::get_read_structure();
1068    }
1069
1070    /**
1071     * Get data for the daily calendar view.
1072     *
1073     * @param   int     $year The year to be shown
1074     * @param   int     $month The month to be shown
1075     * @param   int     $day The day to be shown
1076     * @param   int     $courseid The course to be included
1077     * @return  array
1078     */
1079    public static function get_calendar_day_view($year, $month, $day, $courseid, $categoryid) {
1080        global $DB, $USER, $PAGE;
1081
1082        // Parameter validation.
1083        $params = self::validate_parameters(self::get_calendar_day_view_parameters(), [
1084            'year' => $year,
1085            'month' => $month,
1086            'day' => $day,
1087            'courseid' => $courseid,
1088            'categoryid' => $categoryid,
1089        ]);
1090
1091        $context = \context_user::instance($USER->id);
1092        self::validate_context($context);
1093
1094        $type = \core_calendar\type_factory::get_calendar_instance();
1095
1096        $time = $type->convert_to_timestamp($params['year'], $params['month'], $params['day']);
1097        $calendar = \calendar_information::create($time, $params['courseid'], $params['categoryid']);
1098        self::validate_context($calendar->context);
1099
1100        list($data, $template) = calendar_get_view($calendar, 'day');
1101
1102        return $data;
1103    }
1104
1105    /**
1106     * Returns description of method parameters.
1107     *
1108     * @return external_function_parameters
1109     */
1110    public static function get_calendar_day_view_parameters() {
1111        return new external_function_parameters(
1112            [
1113                'year' => new external_value(PARAM_INT, 'Year to be viewed', VALUE_REQUIRED),
1114                'month' => new external_value(PARAM_INT, 'Month to be viewed', VALUE_REQUIRED),
1115                'day' => new external_value(PARAM_INT, 'Day to be viewed', VALUE_REQUIRED),
1116                'courseid' => new external_value(PARAM_INT, 'Course being viewed', VALUE_DEFAULT, SITEID, NULL_ALLOWED),
1117                'categoryid' => new external_value(PARAM_INT, 'Category being viewed', VALUE_DEFAULT, null, NULL_ALLOWED),
1118            ]
1119        );
1120    }
1121
1122    /**
1123     * Returns description of method result value.
1124     *
1125     * @return external_description
1126     */
1127    public static function get_calendar_day_view_returns() {
1128        return \core_calendar\external\calendar_day_exporter::get_read_structure();
1129    }
1130
1131
1132    /**
1133     * Returns description of method parameters.
1134     *
1135     * @return external_function_parameters
1136     */
1137    public static function update_event_start_day_parameters() {
1138        return new external_function_parameters(
1139            [
1140                'eventid' => new external_value(PARAM_INT, 'Id of event to be updated', VALUE_REQUIRED),
1141                'daytimestamp' => new external_value(PARAM_INT, 'Timestamp for the new start day', VALUE_REQUIRED),
1142            ]
1143        );
1144    }
1145
1146    /**
1147     * Change the start day for the given calendar event to the day that
1148     * corresponds with the provided timestamp.
1149     *
1150     * The timestamp only needs to be anytime within the desired day as only
1151     * the date data is extracted from it.
1152     *
1153     * The event's original time of day is maintained, only the date is shifted.
1154     *
1155     * @param int $eventid Id of event to be updated
1156     * @param int $daytimestamp Timestamp for the new start day
1157     * @return  array
1158     */
1159    public static function update_event_start_day($eventid, $daytimestamp) {
1160        global $USER, $PAGE;
1161
1162        // Parameter validation.
1163        $params = self::validate_parameters(self::update_event_start_day_parameters(), [
1164            'eventid' => $eventid,
1165            'daytimestamp' => $daytimestamp,
1166        ]);
1167
1168        $vault = event_container::get_event_vault();
1169        $mapper = event_container::get_event_mapper();
1170        $event = $vault->get_event_by_id($eventid);
1171
1172        if (!$event) {
1173            throw new \moodle_exception('Unable to find event with id ' . $eventid);
1174        }
1175
1176        $legacyevent = $mapper->from_event_to_legacy_event($event);
1177
1178        if (!calendar_edit_event_allowed($legacyevent, true)) {
1179            print_error('nopermissiontoupdatecalendar');
1180        }
1181
1182        self::validate_context($legacyevent->context);
1183
1184        $newdate = usergetdate($daytimestamp);
1185        $startdatestring = implode('-', [$newdate['year'], $newdate['mon'], $newdate['mday']]);
1186        $startdate = new DateTimeImmutable($startdatestring);
1187        $event = local_api::update_event_start_day($event, $startdate);
1188        $cache = new events_related_objects_cache([$event]);
1189        $relatedobjects = [
1190            'context' => $cache->get_context($event),
1191            'course' => $cache->get_course($event),
1192        ];
1193        $exporter = new event_exporter($event, $relatedobjects);
1194        $renderer = $PAGE->get_renderer('core_calendar');
1195
1196        return array('event' => $exporter->export($renderer));
1197    }
1198
1199    /**
1200     * Returns description of method result value.
1201     *
1202     * @return external_description
1203     */
1204    public static function update_event_start_day_returns() {
1205        return new external_single_structure(
1206            array(
1207                'event' => event_exporter::get_read_structure()
1208            )
1209        );
1210    }
1211
1212    /**
1213     * Get data for the monthly calendar view.
1214     *
1215     * @param   int     $courseid The course to be included
1216     * @param   int     $categoryid The category to be included
1217     * @return  array
1218     */
1219    public static function get_calendar_upcoming_view($courseid, $categoryid) {
1220        global $DB, $USER, $PAGE;
1221
1222        // Parameter validation.
1223        $params = self::validate_parameters(self::get_calendar_upcoming_view_parameters(), [
1224            'courseid' => $courseid,
1225            'categoryid' => $categoryid,
1226        ]);
1227
1228        $context = \context_user::instance($USER->id);
1229        self::validate_context($context);
1230        $PAGE->set_url('/calendar/');
1231
1232        $calendar = \calendar_information::create(time(), $params['courseid'], $params['categoryid']);
1233        self::validate_context($calendar->context);
1234
1235        list($data, $template) = calendar_get_view($calendar, 'upcoming');
1236
1237        return $data;
1238    }
1239
1240    /**
1241     * Returns description of method parameters.
1242     *
1243     * @return external_function_parameters
1244     */
1245    public static function get_calendar_upcoming_view_parameters() {
1246        return new external_function_parameters(
1247            [
1248                'courseid' => new external_value(PARAM_INT, 'Course being viewed', VALUE_DEFAULT, SITEID, NULL_ALLOWED),
1249                'categoryid' => new external_value(PARAM_INT, 'Category being viewed', VALUE_DEFAULT, null, NULL_ALLOWED),
1250            ]
1251        );
1252    }
1253
1254    /**
1255     * Returns description of method result value.
1256     *
1257     * @return external_description
1258     */
1259    public static function get_calendar_upcoming_view_returns() {
1260        return \core_calendar\external\calendar_upcoming_exporter::get_read_structure();
1261    }
1262
1263
1264    /**
1265     * Returns description of method parameters.
1266     *
1267     * @return external_function_parameters.
1268     * @since  Moodle 3.7
1269     */
1270    public static function get_calendar_access_information_parameters() {
1271        return new external_function_parameters(
1272            [
1273                'courseid' => new external_value(PARAM_INT, 'Course to check, empty for site calendar events.', VALUE_DEFAULT, 0),
1274            ]
1275        );
1276    }
1277
1278    /**
1279     * Convenience function to retrieve some permissions information for the given course calendar.
1280     *
1281     * @param int $courseid Course to check, empty for site.
1282     * @return array The access information
1283     * @throws moodle_exception
1284     * @since  Moodle 3.7
1285     */
1286    public static function get_calendar_access_information($courseid = 0) {
1287
1288        $params = self::validate_parameters(self::get_calendar_access_information_parameters(), ['courseid' => $courseid]);
1289
1290        if (empty($params['courseid']) || $params['courseid'] == SITEID) {
1291            $context = \context_system::instance();
1292        } else {
1293            $context = \context_course::instance($params['courseid']);
1294        }
1295
1296        self::validate_context($context);
1297
1298        return [
1299            'canmanageentries' => has_capability('moodle/calendar:manageentries', $context),
1300            'canmanageownentries' => has_capability('moodle/calendar:manageownentries', $context),
1301            'canmanagegroupentries' => has_capability('moodle/calendar:managegroupentries', $context),
1302            'warnings' => [],
1303        ];
1304    }
1305
1306    /**
1307     * Returns description of method result value.
1308     *
1309     * @return external_description.
1310     * @since  Moodle 3.7
1311     */
1312    public static function  get_calendar_access_information_returns() {
1313
1314        return new external_single_structure(
1315            [
1316                'canmanageentries' => new external_value(PARAM_BOOL, 'Whether the user can manage entries.'),
1317                'canmanageownentries' => new external_value(PARAM_BOOL, 'Whether the user can manage its own entries.'),
1318                'canmanagegroupentries' => new external_value(PARAM_BOOL, 'Whether the user can manage group entries.'),
1319                'warnings' => new external_warnings(),
1320            ]
1321        );
1322    }
1323
1324    /**
1325     * Returns description of method parameters.
1326     *
1327     * @return external_function_parameters.
1328     * @since  Moodle 3.7
1329     */
1330    public static function get_allowed_event_types_parameters() {
1331        return new external_function_parameters(
1332            [
1333                'courseid' => new external_value(PARAM_INT, 'Course to check, empty for site.', VALUE_DEFAULT, 0),
1334            ]
1335        );
1336    }
1337
1338    /**
1339     * Get the type of events a user can create in the given course.
1340     *
1341     * @param int $courseid Course to check, empty for site.
1342     * @return array The types allowed
1343     * @throws moodle_exception
1344     * @since  Moodle 3.7
1345     */
1346    public static function get_allowed_event_types($courseid = 0) {
1347
1348        $params = self::validate_parameters(self::get_allowed_event_types_parameters(), ['courseid' => $courseid]);
1349
1350        if (empty($params['courseid']) || $params['courseid'] == SITEID) {
1351            $context = \context_system::instance();
1352        } else {
1353            $context = \context_course::instance($params['courseid']);
1354        }
1355
1356        self::validate_context($context);
1357
1358        $allowedeventtypes = array_filter(calendar_get_allowed_event_types($params['courseid']));
1359
1360        return [
1361            'allowedeventtypes' => array_keys($allowedeventtypes),
1362            'warnings' => [],
1363        ];
1364    }
1365
1366    /**
1367     * Returns description of method result value.
1368     *
1369     * @return external_description.
1370     * @since  Moodle 3.7
1371     */
1372    public static function  get_allowed_event_types_returns() {
1373
1374        return new external_single_structure(
1375            [
1376                'allowedeventtypes' => new external_multiple_structure(
1377                    new external_value(PARAM_NOTAGS, 'Allowed event types to be created in the given course.')
1378                ),
1379                'warnings' => new external_warnings(),
1380            ]
1381        );
1382    }
1383
1384    /**
1385     * Convert the specified dates into unix timestamps.
1386     *
1387     * @param   array $datetimes Array of arrays containing date time details, each in the format:
1388     *           ['year' => a, 'month' => b, 'day' => c,
1389     *            'hour' => d (optional), 'minute' => e (optional), 'key' => 'x' (optional)]
1390     * @return  array Provided array of dates converted to unix timestamps
1391     * @throws moodle_exception If one or more of the dates provided does not convert to a valid timestamp.
1392     */
1393    public static function get_timestamps($datetimes) {
1394        $params = self::validate_parameters(self::get_timestamps_parameters(), ['data' => $datetimes]);
1395
1396        $type = \core_calendar\type_factory::get_calendar_instance();
1397        $timestamps = ['timestamps' => []];
1398
1399        foreach ($params['data'] as $key => $datetime) {
1400            $hour = $datetime['hour'] ?? 0;
1401            $minute = $datetime['minute'] ?? 0;
1402
1403            try {
1404                $timestamp = $type->convert_to_timestamp(
1405                    $datetime['year'], $datetime['month'], $datetime['day'], $hour, $minute);
1406
1407                $timestamps['timestamps'][] = [
1408                    'key' => $datetime['key'] ?? $key,
1409                    'timestamp' => $timestamp,
1410                ];
1411
1412            } catch (Exception $e) {
1413                throw new moodle_exception('One or more of the dates provided were invalid');
1414            }
1415        }
1416
1417        return $timestamps;
1418    }
1419
1420    /**
1421     * Describes the parameters for get_timestamps.
1422     *
1423     * @return external_function_parameters
1424     */
1425    public static function get_timestamps_parameters() {
1426        return new external_function_parameters ([
1427            'data' => new external_multiple_structure(
1428                new external_single_structure(
1429                    [
1430                        'key' => new external_value(PARAM_ALPHANUMEXT, 'key', VALUE_OPTIONAL),
1431                        'year' => new external_value(PARAM_INT, 'year'),
1432                        'month' => new external_value(PARAM_INT, 'month'),
1433                        'day' => new external_value(PARAM_INT, 'day'),
1434                        'hour' => new external_value(PARAM_INT, 'hour', VALUE_OPTIONAL),
1435                        'minute' => new external_value(PARAM_INT, 'minute', VALUE_OPTIONAL),
1436                    ]
1437                )
1438            )
1439        ]);
1440    }
1441
1442    /**
1443     * Describes the timestamps return format.
1444     *
1445     * @return external_single_structure
1446     */
1447    public static function get_timestamps_returns() {
1448        return new external_single_structure(
1449            [
1450                'timestamps' => new external_multiple_structure(
1451                    new external_single_structure(
1452                        [
1453                            'key' => new external_value(PARAM_ALPHANUMEXT, 'Timestamp key'),
1454                            'timestamp' => new external_value(PARAM_INT, 'Unix timestamp'),
1455                        ]
1456                    )
1457                )
1458            ]
1459        );
1460    }
1461}
1462