MDL-60960 calendar: check enrolment at last chance in container
[moodle.git] / calendar / classes / local / event / container.php
blob597eb70889f8321385dc1a048e025b17cff75bf7
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.
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/>.
17 /**
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;
42 /**
43 * Core container.
45 * @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
46 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
48 class container {
49 /**
50 * @var event_factory $eventfactory Event factory.
52 protected static $eventfactory;
54 /**
55 * @var event_mapper $eventmapper Event mapper.
57 protected static $eventmapper;
59 /**
60 * @var action_factory $actionfactory Action factory.
62 protected static $actionfactory;
64 /**
65 * @var event_vault $eventvault Event vault.
67 protected static $eventvault;
69 /**
70 * @var raw_event_retrieval_strategy $eventretrievalstrategy Event retrieval strategy.
72 protected static $eventretrievalstrategy;
74 /**
75 * @var \stdClass[] An array of cached courses to use with the event factory.
77 protected static $coursecache = array();
79 /**
80 * @var \stdClass[] An array of cached modules to use with the event factory.
82 protected static $modulecache = array();
84 /**
85 * Initialises the dependency graph if it hasn't yet been.
87 private static function init() {
88 if (empty(self::$eventfactory)) {
89 self::$actionfactory = new action_factory();
90 self::$eventmapper = new event_mapper(
91 // The event mapper we return from here needs to know how to
92 // make events, so it needs an event factory. However we can't
93 // give it the same one as we store and return in the container
94 // as that one uses all our plumbing to control event visibility.
96 // So we make a new even factory that doesn't do anyting other than
97 // return the instance.
98 new event_factory(
99 // Never apply actions, simply return.
100 function(event_interface $event) {
101 return $event;
103 // Never hide an event.
104 function() {
105 return true;
107 // Never bail out early when instantiating an event.
108 function() {
109 return false;
111 self::$coursecache,
112 self::$modulecache
116 self::$eventfactory = new event_factory(
117 [self::class, 'apply_component_provide_event_action'],
118 [self::class, 'apply_component_is_event_visible'],
119 function ($dbrow) {
120 if (!empty($dbrow->categoryid)) {
121 // This is a category event. Check that the category is visible to this user.
122 $category = \coursecat::get($dbrow->categoryid, IGNORE_MISSING, true);
124 if (empty($category) || !$category->is_uservisible()) {
125 return true;
129 // At present we only have a bail-out check for events in course modules.
130 if (empty($dbrow->modulename)) {
131 return false;
134 $instances = get_fast_modinfo($dbrow->courseid)->instances;
136 // If modinfo doesn't know about the module, we should ignore it.
137 if (!isset($instances[$dbrow->modulename]) || !isset($instances[$dbrow->modulename][$dbrow->instance])) {
138 return true;
141 $cm = $instances[$dbrow->modulename][$dbrow->instance];
143 // If the module is not visible to the current user, we should ignore it.
144 // We have to check enrolment here as well because the uservisible check
145 // looks for the "view" capability however some activities (such as Lesson)
146 // have that capability set on the "Authenticated User" role rather than
147 // on "Student" role, which means uservisible returns true even when the user
148 // is no longer enrolled in the course.
149 // So, with the following we are checking -
150 // 1) Only process modules if $cm->uservisible is true.
151 // 2) Only process modules for courses a user has the capability to view OR they are enrolled in.
152 // 3) Only process modules for courses that are visible OR if the course is not visible, the user
153 // has the capability to view hidden courses.
154 if (!$cm->uservisible) {
155 return true;
158 $coursecontext = \context_course::instance($dbrow->courseid);
159 if (!$cm->get_course()->visible && !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
160 return true;
163 if (!has_capability('moodle/course:view', $coursecontext) && !is_enrolled($coursecontext)) {
164 return true;
167 // Ok, now check if we are looking at a completion event.
168 if ($dbrow->eventtype === \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED) {
169 // Need to have completion enabled before displaying these events.
170 $course = new \stdClass();
171 $course->id = $dbrow->courseid;
172 $completion = new \completion_info($course);
174 return (bool) !$completion->is_enabled($cm);
177 return false;
179 self::$coursecache,
180 self::$modulecache
184 if (empty(self::$eventvault)) {
185 self::$eventretrievalstrategy = new raw_event_retrieval_strategy();
186 self::$eventvault = new event_vault(self::$eventfactory, self::$eventretrievalstrategy);
191 * Reset all static caches, called between tests.
193 public static function reset_caches() {
194 self::$eventfactory = null;
195 self::$eventmapper = null;
196 self::$eventvault = null;
197 self::$actionfactory = null;
198 self::$eventretrievalstrategy = null;
199 self::$coursecache = [];
200 self::$modulecache = [];
204 * Gets the event factory.
206 * @return event_factory
208 public static function get_event_factory() {
209 self::init();
210 return self::$eventfactory;
214 * Gets the event mapper.
216 * @return event_mapper
218 public static function get_event_mapper() {
219 self::init();
220 return self::$eventmapper;
224 * Return an event vault.
226 * @return event_vault
228 public static function get_event_vault() {
229 self::init();
230 return self::$eventvault;
234 * Calls callback 'core_calendar_provide_event_action' from the component responsible for the event
236 * If no callback is present or callback returns null, there is no action on the event
237 * and it will not be displayed on the dashboard.
239 * @param event_interface $event
240 * @return action_event|event_interface
242 public static function apply_component_provide_event_action(event_interface $event) {
243 // Callbacks will get supplied a "legacy" version
244 // of the event class.
245 $mapper = self::$eventmapper;
246 $action = null;
247 if ($event->get_course_module()) {
248 // TODO MDL-58866 Only activity modules currently support this callback.
249 // Any other event will not be displayed on the dashboard.
250 $action = component_callback(
251 'mod_' . $event->get_course_module()->get('modname'),
252 'core_calendar_provide_event_action',
254 $mapper->from_event_to_legacy_event($event),
255 self::$actionfactory
260 // If we get an action back, return an action event, otherwise
261 // continue piping through the original event.
263 // If a module does not implement the callback, component_callback
264 // returns null.
265 return $action ? new action_event($event, $action) : $event;
269 * Calls callback 'core_calendar_is_event_visible' from the component responsible for the event
271 * The visibility callback is optional, if not present it is assumed as visible.
272 * If it is an actionable event but the get_item_count() returns 0 the visibility
273 * is set to false.
275 * @param event_interface $event
276 * @return bool
278 public static function apply_component_is_event_visible(event_interface $event) {
279 $mapper = self::$eventmapper;
280 $eventvisible = null;
281 if ($event->get_course_module()) {
282 // TODO MDL-58866 Only activity modules currently support this callback.
283 $eventvisible = component_callback(
284 'mod_' . $event->get_course_module()->get('modname'),
285 'core_calendar_is_event_visible',
287 $mapper->from_event_to_legacy_event($event)
292 // Do not display the event if there is nothing to action.
293 if ($event instanceof action_event_interface && $event->get_action()->get_item_count() === 0) {
294 return false;
297 // Module does not implement the callback, event should be visible.
298 if (is_null($eventvisible)) {
299 return true;
302 return $eventvisible ? true : false;