2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * Core container for calendar events.
20 * The purpose of this class is simply to wire together the various
21 * implementations of calendar event components to produce a solution
22 * to the problems Moodle core wants to solve.
24 * @package core_calendar
25 * @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29 namespace core_calendar\local\event;
31 defined('MOODLE_INTERNAL') || die();
33 use core_calendar\action_factory;
34 use core_calendar\local\event\data_access\event_vault;
35 use core_calendar\local\event\entities\action_event;
36 use core_calendar\local\event\entities\action_event_interface;
37 use core_calendar\local\event\entities\event_interface;
38 use core_calendar\local\event\factories\event_factory;
39 use core_calendar\local\event\mappers\event_mapper;
40 use core_calendar\local\event\strategies\raw_event_retrieval_strategy;
45 * @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
46 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
50 * @var event_factory $eventfactory Event factory.
52 protected static $eventfactory;
55 * @var event_mapper $eventmapper Event mapper.
57 protected static $eventmapper;
60 * @var action_factory $actionfactory Action factory.
62 protected static $actionfactory;
65 * @var event_vault $eventvault Event vault.
67 protected static $eventvault;
70 * @var raw_event_retrieval_strategy $eventretrievalstrategy Event retrieval strategy.
72 protected static $eventretrievalstrategy;
75 * @var array A list of callbacks to use.
77 protected static $callbacks = array();
80 * @var \stdClass[] An array of cached courses to use with the event factory.
82 protected static $coursecache = array();
85 * @var \stdClass[] An array of cached modules to use with the event factory.
87 protected static $modulecache = array();
90 * Initialises the dependency graph if it hasn't yet been.
92 private static function init() {
93 if (empty(self::$eventfactory)) {
94 // When testing the container's components, we need to make sure
95 // the callback implementations in modules are not executed, since
96 // we cannot control their output from PHPUnit. To do this we have
97 // a set of 'testing' callbacks that the factory can use. This way
98 // we know exactly how the factory behaves when being tested.
99 $getcallback = function($which) {
100 return self::$callbacks[PHPUNIT_TEST ? 'testing' : 'production'][$which];
103 self::initcallbacks();
104 self::$actionfactory = new action_factory();
105 self::$eventmapper = new event_mapper(
106 // The event mapper we return from here needs to know how to
107 // make events, so it needs an event factory. However we can't
108 // give it the same one as we store and return in the container
109 // as that one uses all our plumbing to control event visibility.
111 // So we make a new even factory that doesn't do anyting other than
112 // return the instance.
114 // Never apply actions, simply return.
115 function(event_interface $event) {
118 // Never hide an event.
122 // Never bail out early when instantiating an event.
131 self::$eventfactory = new event_factory(
132 $getcallback('action'),
133 $getcallback('visibility'),
135 // At present we only handle callbacks in course modules.
136 if (empty($dbrow->modulename)) {
140 $instances = get_fast_modinfo($dbrow->courseid)->instances;
142 // If modinfo doesn't know about the module, we should ignore it.
143 if (!isset($instances[$dbrow->modulename]) || !isset($instances[$dbrow->modulename][$dbrow->instance])) {
147 $cm = $instances[$dbrow->modulename][$dbrow->instance];
149 // If the module is not visible to the current user, we should ignore it.
150 // We have to check enrolment here as well because the uservisible check
151 // looks for the "view" capability however some activities (such as Lesson)
152 // have that capability set on the "Authenticated User" role rather than
153 // on "Student" role, which means uservisible returns true even when the user
154 // is no longer enrolled in the course.
155 $modulecontext = \context_module::instance($cm->id);
156 // A user with the 'moodle/course:view' capability is able to see courses
157 // that they are not a participant in.
158 $canseecourse = (has_capability('moodle/course:view', $modulecontext) || is_enrolled($modulecontext));
159 if (!$cm->uservisible || !$canseecourse) {
163 // Ok, now check if we are looking at a completion event.
164 if ($dbrow->eventtype === \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED) {
165 // Need to have completion enabled before displaying these events.
166 $course = new \stdClass();
167 $course->id = $dbrow->courseid;
168 $completion = new \completion_info($course);
170 return (bool) !$completion->is_enabled($cm);
180 if (empty(self::$eventvault)) {
181 self::$eventretrievalstrategy = new raw_event_retrieval_strategy();
182 self::$eventvault = new event_vault(self::$eventfactory, self::$eventretrievalstrategy);
187 * Gets the event factory.
189 * @return event_factory
191 public static function get_event_factory() {
193 return self::$eventfactory;
197 * Gets the event mapper.
199 * @return event_mapper
201 public static function get_event_mapper() {
203 return self::$eventmapper;
207 * Return an event vault.
209 * @return event_vault
211 public static function get_event_vault() {
213 return self::$eventvault;
217 * Initialises the callbacks.
219 * There are two sets here, one is used during PHPUnit runs.
220 * See the comment at the start of the init method for more
223 private static function initcallbacks() {
224 self::$callbacks = array(
226 // Always return an action event.
227 'action' => function (event_interface $event) {
228 return new action_event(
230 new \core_calendar\local\event\value_objects\action(
232 new \moodle_url('http://example.com'),
237 // Always be visible.
238 'visibility' => function (event_interface $event) {
242 'production' => array(
243 // This function has type event_interface -> event_interface.
244 // This is enforced by the event_factory.
245 'action' => function (event_interface $event) {
246 // Callbacks will get supplied a "legacy" version
247 // of the event class.
248 $mapper = self::$eventmapper;
249 $action = component_callback(
250 'mod_' . $event->get_course_module()->get('modname'),
251 'core_calendar_provide_event_action',
253 $mapper->from_event_to_legacy_event($event),
258 // If we get an action back, return an action event, otherwise
259 // continue piping through the original event.
261 // If a module does not implement the callback, component_callback
263 return $action ? new action_event($event, $action) : $event;
265 // This function has type event_interface -> bool.
266 // This is enforced by the event_factory.
267 'visibility' => function (event_interface $event) {
268 $mapper = self::$eventmapper;
269 $eventvisible = component_callback(
270 'mod_' . $event->get_course_module()->get('modname'),
271 'core_calendar_is_event_visible',
273 $mapper->from_event_to_legacy_event($event)
277 // Do not display the event if there is nothing to action.
278 if ($event instanceof action_event_interface && $event->get_action()->get_item_count() === 0) {
282 // Module does not implement the callback, event should be visible.
283 if (is_null($eventvisible)) {
287 return $eventvisible ? true : false;