MDL-57502 core_calendar: add calendar event exporters
authorRyan Wyllie <ryan@moodle.com>
Tue, 17 Jan 2017 03:24:09 +0000 (03:24 +0000)
committerDamyon Wiese <damyon@moodle.com>
Mon, 3 Apr 2017 03:36:07 +0000 (11:36 +0800)
Part of MDL-55611 epic.

calendar/classes/external/event_action_exporter.php [new file with mode: 0644]
calendar/classes/external/event_exporter.php [new file with mode: 0644]
calendar/classes/external/event_icon_exporter.php [new file with mode: 0644]
calendar/classes/external/events_exporter.php [new file with mode: 0644]
calendar/classes/external/events_related_objects_cache.php [new file with mode: 0644]
lang/en/calendar.php

diff --git a/calendar/classes/external/event_action_exporter.php b/calendar/classes/external/event_action_exporter.php
new file mode 100644 (file)
index 0000000..53624d0
--- /dev/null
@@ -0,0 +1,78 @@
+<?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/>.
+
+/**
+ * Contains event class for displaying a calendar event's action.
+ *
+ * @package   core_calendar
+ * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_calendar\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core\external\exporter;
+use \core_calendar\local\interfaces\action_interface;
+
+/**
+ * Class for displaying a calendar event's action.
+ *
+ * @package   core_calendar
+ * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class event_action_exporter extends exporter {
+
+    /**
+     * Constructor.
+     *
+     * @param action_interface $action
+     */
+    public function __construct(action_interface $action, $related = []) {
+        $data = new \stdClass();
+        $data->name = $action->get_name();
+        $data->url = $action->get_url()->out(true);
+        $data->itemcount = $action->get_item_count();
+
+        parent::__construct($data, $related);
+    }
+
+    /**
+     * Return the list of properties.
+     *
+     * @return array
+     */
+    protected static function define_properties() {
+        return [
+            'name' => ['type' => PARAM_TEXT],
+            'url' => ['type' => PARAM_URL],
+            'itemcount' => ['type' => PARAM_INT],
+        ];
+    }
+
+    /**
+     * Returns a list of objects that are related.
+     *
+     * @return array
+     */
+    protected static function define_related() {
+        return [
+            'context' => 'context',
+        ];
+    }
+}
diff --git a/calendar/classes/external/event_exporter.php b/calendar/classes/external/event_exporter.php
new file mode 100644 (file)
index 0000000..67213e8
--- /dev/null
@@ -0,0 +1,216 @@
+<?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/>.
+
+/**
+ * Contains event class for displaying a calendar event.
+ *
+ * @package   core_calendar
+ * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_calendar\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core\external\exporter;
+use \core_calendar\local\interfaces\event_interface;
+use \core_calendar\local\interfaces\action_event_interface;
+use \core_course\external\course_summary_exporter;
+use \renderer_base;
+
+/**
+ * Class for displaying a calendar event.
+ *
+ * @package   core_calendar
+ * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class event_exporter extends exporter {
+
+    /**
+     * @var event_interface $event
+     */
+    protected $event;
+
+    /**
+     * Constructor.
+     *
+     * @param event_interface $event
+     */
+    public function __construct(event_interface $event, $related = []) {
+        $this->event = $event;
+
+        $starttimestamp = $event->get_times()->get_start_time()->getTimestamp();
+        $endtimestamp = $event->get_times()->get_end_time()->getTimestamp();
+        $groupid = $event->get_group() ? $event->get_group()->get('id') : null;
+        $userid = $event->get_user() ? $event->get_user()->get('id') : null;
+
+        $data = new \stdClass();
+        $data->id = $event->get_id();
+        $data->name = $event->get_name();
+        $data->description = $event->get_description()->get_value();
+        $data->descriptionformat = $event->get_description()->get_format();
+        $data->groupid = $groupid;
+        $data->userid = $userid;
+        $data->eventtype = $event->get_type();
+        $data->timestart = $starttimestamp;
+        $data->timeduration = $endtimestamp - $starttimestamp;
+        $data->timesort = $event->get_times()->get_sort_time()->getTimestamp();
+        $data->visible = $event->is_visible();
+        $data->timemodified = $event->get_times()->get_modified_time()->getTimestamp();
+
+        if ($repeats = $event->get_repeats()) {
+            $data->repeatid = $repeats->get_id();
+        }
+
+        if ($cm = $event->get_course_module()) {
+            $data->modulename = $cm->get('modname');
+            $data->instance = $cm->get('id');
+        }
+
+        parent::__construct($data, $related);
+    }
+
+    /**
+     * Return the list of properties.
+     *
+     * @return array
+     */
+    protected static function define_properties() {
+        return [
+            'id' => ['type' => PARAM_INT],
+            'name' => ['type' => PARAM_TEXT],
+            'description' => [
+                'type' => PARAM_RAW,
+                'optional' => true,
+                'default' => null,
+                'null' => NULL_ALLOWED
+            ],
+            'descriptionformat' => [
+                'type' => PARAM_INT,
+                'optional' => true,
+                'default' => null,
+                'null' => NULL_ALLOWED
+            ],
+            'groupid' => [
+                'type' => PARAM_INT,
+                'optional' => true,
+                'default' => null,
+                'null' => NULL_ALLOWED
+            ],
+            'userid' => [
+                'type' => PARAM_INT,
+                'optional' => true,
+                'default' => null,
+                'null' => NULL_ALLOWED
+            ],
+            'repeatid' => [
+                'type' => PARAM_INT,
+                'optional' => true,
+                'default' => null,
+                'null' => NULL_ALLOWED
+            ],
+            'modulename' => [
+                'type' => PARAM_TEXT,
+                'optional' => true,
+                'default' => null,
+                'null' => NULL_ALLOWED
+            ],
+            'instance' => [
+                'type' => PARAM_INT,
+                'optional' => true,
+                'default' => null,
+                'null' => NULL_ALLOWED
+            ],
+            'eventtype' => ['type' => PARAM_TEXT],
+            'timestart' => ['type' => PARAM_INT],
+            'timeduration' => ['type' => PARAM_INT],
+            'timesort' => ['type' => PARAM_INT],
+            'visible' => ['type' => PARAM_INT],
+            'timemodified' => ['type' => PARAM_INT],
+        ];
+    }
+
+    /**
+     * Return the list of additional properties.
+     *
+     * @return array
+     */
+    protected static function define_other_properties() {
+        return [
+            'url' => ['type' => PARAM_URL],
+            'enddate' => ['type' => PARAM_TEXT],
+            'icon' => [
+                'type' => event_icon_exporter::read_properties_definition(),
+            ],
+            'action' => [
+                'type' => event_action_exporter::read_properties_definition(),
+                'optional' => true,
+            ],
+            'course' => [
+                'type' => course_summary_exporter::read_properties_definition(),
+                'optional' => true,
+            ]
+        ];
+    }
+
+    /**
+     * Get the additional values to inject while exporting.
+     *
+     * @param renderer_base $output The renderer.
+     * @return array Keys are the property names, values are their values.
+     */
+    protected function get_other_values(renderer_base $output) {
+        $values = [];
+        $event = $this->event;
+        $context = $this->related['context'];
+        $modulename = $event->get_course_module()->get('modname');
+        $moduleid = $event->get_course_module()->get('id');
+        $timesort = $event->get_times()->get_sort_time()->getTimestamp();
+        $url = new \moodle_url(sprintf('/mod/%s/view.php', $modulename), ['id' => $moduleid]);
+        $iconexporter = new event_icon_exporter($event, ['context' => $context]);
+
+        $values['url'] = $url->out(false);
+        $values['enddate'] = userdate($timesort, get_string('strftimerecent'));
+        $values['icon'] = $iconexporter->export($output);
+
+        if ($event instanceof action_event_interface) {
+            $actionexporter = new event_action_exporter($event->get_action(),
+                ['context' => $context]);
+            $values['action'] = $actionexporter->export($output);
+        }
+
+        if ($course = $this->related['course']) {
+            $coursesummaryexporter = new course_summary_exporter($course, ['context' => $context]);
+            $values['course'] = $coursesummaryexporter->export($output);
+        }
+
+        return $values;
+    }
+
+    /**
+     * Returns a list of objects that are related.
+     *
+     * @return array
+     */
+    protected static function define_related() {
+        return [
+            'context' => 'context',
+            'course' => 'stdClass?',
+        ];
+    }
+}
diff --git a/calendar/classes/external/event_icon_exporter.php b/calendar/classes/external/event_icon_exporter.php
new file mode 100644 (file)
index 0000000..48c1a87
--- /dev/null
@@ -0,0 +1,123 @@
+<?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/>.
+
+/**
+ * Contains event class for displaying a calendar event's icon.
+ *
+ * @package   core_calendar
+ * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_calendar\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core\external\exporter;
+use \core_calendar\local\interfaces\event_interface;
+
+/**
+ * Class for displaying a calendar event's icon.
+ *
+ * @package   core_calendar
+ * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class event_icon_exporter extends exporter {
+
+    /**
+     * Constructor.
+     *
+     * @param event_interface $event
+     */
+    public function __construct(event_interface $event, $related = []) {
+        $coursemodule = $event->get_course_module();
+        $course = $event->get_course();
+        $courseid = $course ? $course->get('id') : null;
+        $group = $event->get_group();
+        $groupid = $group ? $group->get('id') : null;
+        $user = $event->get_user();
+        $userid = $user ? $user->get('id') : null;
+        $isactivityevent = !empty($coursemodule);
+        $isglobalevent = ($course && $courseid == SITEID);
+        $iscourseevent = ($course && !empty($courseid) && $courseid != SITEID && $group && empty($groupid));
+        $isgroupevent = ($group && !empty($groupid));
+        $isuserevent = ($user && !empty($userid));
+
+        if ($isactivityevent) {
+            $key = 'icon';
+            $component = $coursemodule->get('modname');
+
+            if (get_string_manager()->string_exists($event->get_type(), $component)) {
+                $alttext = get_string($event->get_type(), $component);
+            } else {
+                $alttext = get_string('activityevent', 'calendar');
+            }
+        } else if ($isglobalevent) {
+            $key = 'i/siteevent';
+            $component = 'core';
+            $alttext = get_string('globalevent', 'calendar');
+        } else if ($iscourseevent) {
+            $key = 'i/courseevent';
+            $component = 'core';
+            $alttext = get_string('courseevent', 'calendar');
+        } else if ($isgroupevent) {
+            $key = 'i/groupevent';
+            $component = 'core';
+            $alttext = get_string('groupevent', 'calendar');
+        } else if ($isuserevent) {
+            $key = 'i/userevent';
+            $component = 'core';
+            $alttext = get_string('userevent', 'calendar');
+        } else {
+            // Default to site event icon?
+            $key = 'i/siteevent';
+            $component = 'core';
+            $alttext = get_string('globalevent', 'calendar');
+        }
+
+        $data = new \stdClass();
+        $data->key = $key;
+        $data->component = $component;
+        $data->alttext = $alttext;
+
+        parent::__construct($data, $related);
+    }
+
+    /**
+     * Return the list of properties.
+     *
+     * @return array
+     */
+    protected static function define_properties() {
+        return [
+            'key' => ['type' => PARAM_TEXT],
+            'component' => ['type' => PARAM_TEXT],
+            'alttext' => ['type' => PARAM_TEXT],
+        ];
+    }
+
+    /**
+     * Returns a list of objects that are related.
+     *
+     * @return array
+     */
+    protected static function define_related() {
+        return [
+            'context' => 'context',
+        ];
+    }
+}
diff --git a/calendar/classes/external/events_exporter.php b/calendar/classes/external/events_exporter.php
new file mode 100644 (file)
index 0000000..9198c4c
--- /dev/null
@@ -0,0 +1,121 @@
+<?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/>.
+
+/**
+ * Contains event class for displaying a list of calendar events.
+ *
+ * @package   core_calendar
+ * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_calendar\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core\external\exporter;
+use \renderer_base;
+
+/**
+ * Class for displaying a list of calendar events.
+ *
+ * This class uses the events relateds cache in order to get the related
+ * data for exporting an event without having to naively hit the database
+ * for each event.
+ *
+ * @package   core_calendar
+ * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class events_exporter extends exporter {
+
+    /**
+     * @var array $events An array of event_interface objects.
+     */
+    protected $events;
+
+    /**
+     * Constructor.
+     *
+     * @param array $events An array of event_interface objects
+     * @param array $related An array of related objects
+     */
+    public function __construct(array $events, $related = []) {
+        $this->events = $events;
+        parent::__construct([], $related);
+    }
+
+    /**
+     * Return the list of additional properties.
+     *
+     * @return array
+     */
+    protected static function define_other_properties() {
+        return [
+            'events' => [
+                'type' => event_exporter::read_properties_definition(),
+                'multiple' => true,
+            ],
+            'firstid' => [
+                'type' => PARAM_INT,
+                'null' => NULL_ALLOWED,
+                'default' => null,
+            ],
+            'lastid' => [
+                'type' => PARAM_INT,
+                'null' => NULL_ALLOWED,
+                'default' => null,
+            ],
+        ];
+    }
+
+    /**
+     * Get the additional values to inject while exporting.
+     *
+     * @param renderer_base $output The renderer.
+     * @return array Keys are the property names, values are their values.
+     */
+    protected function get_other_values(renderer_base $output) {
+        $return = [];
+        $cache = $this->related['cache'];
+
+        $return['events'] = array_map(function($event) use ($cache, $output) {
+            $context = $cache->get_context($event);
+            $course = $cache->get_course($event);
+            $exporter = new event_exporter($event, ['context' => $context, 'course' => $course]);
+
+            return $exporter->export($output);
+        }, $this->events);
+
+        if ($count = count($return['events'])) {
+            $return['firstid'] = $return['events'][0]->id;
+            $return['lastid'] = $return['events'][$count - 1]->id;
+        }
+
+        return $return;
+    }
+
+    /**
+     * Returns a list of objects that are related.
+     *
+     * @return array
+     */
+    protected static function define_related() {
+        return [
+            'cache' => 'core_calendar\external\events_related_objects_cache',
+        ];
+    }
+}
diff --git a/calendar/classes/external/events_related_objects_cache.php b/calendar/classes/external/events_related_objects_cache.php
new file mode 100644 (file)
index 0000000..894c6fb
--- /dev/null
@@ -0,0 +1,215 @@
+<?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/>.
+
+/**
+ * Contains event class for providing the related objects when exporting
+ * a list of calendar events.
+ *
+ * @package   core_calendar
+ * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_calendar\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core_calendar\local\interfaces\event_interface;
+
+/**
+ * Class to providing the related objects when exporting
+ * a list of calendar events.
+ *
+ * This class is only meant for use with exporters. It attempts to bulk load
+ * the related objects for a list of events and cache them to avoid having
+ * to query the database when exporting each individual event.
+ *
+ * @package   core_calendar
+ * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class events_related_objects_cache {
+
+    /**
+     * @var array $events The events for which we need related objects.
+     */
+    protected $events;
+
+    /**
+     * @var array $courses The related courses.
+     */
+    protected $courses = null;
+
+    /**
+     * @var array $events The related groups.
+     */
+    protected $groups = null;
+
+    /**
+     * @var array $events The related course modules.
+     */
+    protected $coursemodules = [];
+
+    /**
+     * Constructor.
+     *
+     * @param array $event Array of event_interface events
+     */
+    public function __construct(array $events) {
+        $this->events = $events;
+    }
+
+    /**
+     * Get the related course object for a given event.
+     *
+     * @param event_interface $event.
+     * @return stdClass|null
+     */
+    public function get_course(event_interface $event) {
+        if (is_null($this->courses)) {
+            $this->load_courses();
+        }
+
+        if ($course = $event->get_course()) {
+            $courseid = $course->get('id');
+            return isset($this->courses[$courseid]) ? $this->courses[$courseid] : null;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get the related context for a given event.
+     *
+     * @param event_interface $event.
+     * @return context|null
+     */
+    public function get_context(event_interface $event) {
+        global $USER;
+
+        $courseid = $event->get_course() ? $event->get_course()->get('id') : null;
+        $groupid = $event->get_group() ? $event->get_group()->get('id') : null;
+        $userid = $event->get_user() ? $event->get_user()->get('id') : null;
+        $moduleid = $event->get_course_module() ? $event->get_course_module()->get('id') : null;
+
+        if (!empty($courseid)) {
+            return \context_course::instance($event->get_course()->get('id'));
+        } else if (!empty($groupid)) {
+            $group = $this->get_group($event);
+            return \context_course::instance($group->courseid);
+        } else if (!empty($userid) && $userid == $USER->id) {
+            return \context_user::instance($userid);
+        } else if (!empty($userid) && $userid != $USER->id && $moduleid && $moduleid > 0) {
+            $cm = $this->get_course_module($event);
+            return \context_course::instance($cm->course);
+        } else {
+            return \context_user::instance($userid);
+        }
+    }
+
+    /**
+     * Get the related group object for a given event.
+     *
+     * @param event_interface $event.
+     * @return stdClass|null
+     */
+    public function get_group(event_interface $event) {
+        if (is_null($this->groups)) {
+            $this->load_groups();
+        }
+
+        if ($group = $event->get_group()) {
+            $groupid = $group->get('id');
+            return isset($this->groups[$groupid]) ? $this->groups[$groupid] : null;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get the related course module for a given event.
+     *
+     * @param event_interface $event.
+     * @return stdClass|null
+     */
+    public function get_course_module(event_interface $event) {
+        if (!$event->get_course_module()) {
+            return null;
+        }
+
+        $id = $event->get_course_module()->get('id');
+        $name = $event->get_course_module()->get('modname');
+        $key = $name . '_' . $id;
+
+        if (!isset($this->coursemodules[$key])) {
+            $this->coursemodules[$key] = get_coursemodule_from_instance($name, $id, 0, false, MUST_EXIST);
+        }
+
+        return $this->coursemodules[$key];
+    }
+
+    /**
+     * Load the list of all of the distinct courses required for the
+     * list of provided events and save the result in memory.
+     */
+    protected function load_courses() {
+        global $DB;
+
+        $courseids = [];
+        foreach ($this->events as $event) {
+            if ($course = $event->get_course()) {
+                $id = $course->get('id');
+                $courseids[$id] = true;
+            }
+        }
+
+        if (empty($courseids)) {
+            $this->courses = [];
+            return;
+        }
+
+        list($idsql, $params) = $DB->get_in_or_equal(array_keys($courseids));
+        $sql = "SELECT * FROM {course} WHERE id {$idsql}";
+
+        $this->courses = $DB->get_records_sql($sql, $params);
+    }
+
+    /**
+     * Load the list of all of the distinct groups required for the
+     * list of provided events and save the result in memory.
+     */
+    protected function load_groups() {
+        global $DB;
+
+        $groupids = [];
+        foreach ($this->events as $event) {
+            if ($group = $event->get_group()) {
+                $id = $group->get('id');
+                $groupids[$id] = true;
+            }
+        }
+
+        if (empty($groupids)) {
+            $this->groups = [];
+            return;
+        }
+
+        list($idsql, $params) = $DB->get_in_or_equal(array_keys($groupids));
+        $sql = "SELECT * FROM {groups} WHERE id {$idsql}";
+
+        $this->groups = $DB->get_records_sql($sql, $params);
+    }
+}
index 9782884..e87c008 100644 (file)
@@ -26,6 +26,7 @@ $string['advancedoptions'] = 'Advanced options';
 $string['allday'] = 'All day';
 $string['addevent'] = 'Add events';
 $string['annually'] = 'Annually';
+$string['activityevent'] = 'Activity event';
 $string['calendar'] = 'Calendar';
 $string['calendarheading'] = '{$a} Calendar';
 $string['calendarpreferences'] = 'Calendar preferences';