MDL-58518 calendar: ignore events from courses user not enrolled in
[moodle.git] / calendar / classes / local / event / container.php
CommitLineData
5ca71c2d
CB
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 * Core container for calendar events.
19 *
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.
23 *
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
27 */
28
29namespace core_calendar\local\event;
30
31defined('MOODLE_INTERNAL') || die();
32
33use core_calendar\action_factory;
b6d9b701 34use core_calendar\local\event\data_access\event_vault;
92ac08bc
CB
35use core_calendar\local\event\entities\action_event;
36use core_calendar\local\event\entities\action_event_interface;
42e76c3f 37use core_calendar\local\event\entities\event_interface;
5ca71c2d
CB
38use core_calendar\local\event\factories\event_factory;
39use core_calendar\local\event\mappers\event_mapper;
258a5705 40use core_calendar\local\event\strategies\raw_event_retrieval_strategy;
5ca71c2d
CB
41
42/**
43 * Core container.
44 *
45 * @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
46 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
47 */
d10693cb 48class container {
5ca71c2d
CB
49 /**
50 * @var event_factory $eventfactory Event factory.
51 */
52 protected static $eventfactory;
53
5ca71c2d 54 /**
c28f1077 55 * @var event_mapper $eventmapper Event mapper.
5ca71c2d
CB
56 */
57 protected static $eventmapper;
58
59 /**
60 * @var action_factory $actionfactory Action factory.
61 */
62 protected static $actionfactory;
63
84d865d6 64 /**
42e76c3f 65 * @var event_vault $eventvault Event vault.
84d865d6 66 */
7aedfe32 67 protected static $eventvault;
84d865d6 68
258a5705 69 /**
42e76c3f 70 * @var raw_event_retrieval_strategy $eventretrievalstrategy Event retrieval strategy.
258a5705
CB
71 */
72 protected static $eventretrievalstrategy;
73
c91b4c02
MN
74 /**
75 * @var array A list of callbacks to use.
76 */
77 protected static $callbacks = array();
78
1d364a89 79 /**
20592f5f 80 * @var \stdClass[] An array of cached courses to use with the event factory.
1d364a89
CB
81 */
82 protected static $coursecache = array();
83
e798fa76 84 /**
20592f5f 85 * @var \stdClass[] An array of cached modules to use with the event factory.
e798fa76
CB
86 */
87 protected static $modulecache = array();
88
5ca71c2d
CB
89 /**
90 * Initialises the dependency graph if it hasn't yet been.
91 */
92 private static function init() {
93 if (empty(self::$eventfactory)) {
c28f1077
CB
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.
7aedfe32
CB
99 $getcallback = function($which) {
100 return self::$callbacks[PHPUNIT_TEST ? 'testing' : 'production'][$which];
101 };
102
c91b4c02 103 self::initcallbacks();
5ca71c2d 104 self::$actionfactory = new action_factory();
5ca71c2d 105 self::$eventmapper = new event_mapper(
c28f1077
CB
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.
110 //
111 // So we make a new even factory that doesn't do anyting other than
112 // return the instance.
c91b4c02 113 new event_factory(
c28f1077 114 // Never apply actions, simply return.
c91b4c02
MN
115 function(event_interface $event) {
116 return $event;
7aedfe32 117 },
c28f1077 118 // Never hide an event.
7aedfe32 119 function() {
2a5cce61 120 return true;
1d364a89 121 },
c28f1077 122 // Never bail out early when instantiating an event.
7aedfe32
CB
123 function() {
124 return false;
125 },
e798fa76
CB
126 self::$coursecache,
127 self::$modulecache
c91b4c02
MN
128 )
129 );
2a5cce61 130
c91b4c02 131 self::$eventfactory = new event_factory(
7aedfe32
CB
132 $getcallback('action'),
133 $getcallback('visibility'),
134 function ($dbrow) {
c28f1077 135 // At present we only handle callbacks in course modules.
f8a3e28b
CB
136 if (empty($dbrow->modulename)) {
137 return false;
138 }
139
7aedfe32
CB
140 $instances = get_fast_modinfo($dbrow->courseid)->instances;
141
c28f1077 142 // If modinfo doesn't know about the module, we should ignore it.
7aedfe32
CB
143 if (!isset($instances[$dbrow->modulename]) || !isset($instances[$dbrow->modulename][$dbrow->instance])) {
144 return true;
145 }
146
147 $cm = $instances[$dbrow->modulename][$dbrow->instance];
148
c28f1077 149 // If the module is not visible to the current user, we should ignore it.
405f8491
RW
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) {
74588eec
MN
160 return true;
161 }
162
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);
169
170 return (bool) !$completion->is_enabled($cm);
171 }
172
173 return false;
7aedfe32 174 },
e798fa76
CB
175 self::$coursecache,
176 self::$modulecache
5ca71c2d
CB
177 );
178 }
84d865d6
RW
179
180 if (empty(self::$eventvault)) {
258a5705 181 self::$eventretrievalstrategy = new raw_event_retrieval_strategy();
b6d9b701 182 self::$eventvault = new event_vault(self::$eventfactory, self::$eventretrievalstrategy);
84d865d6 183 }
5ca71c2d
CB
184 }
185
186 /**
187 * Gets the event factory.
188 *
189 * @return event_factory
190 */
191 public static function get_event_factory() {
192 self::init();
193 return self::$eventfactory;
194 }
195
196 /**
7aedfe32 197 * Gets the event mapper.
5ca71c2d 198 *
42e76c3f 199 * @return event_mapper
5ca71c2d
CB
200 */
201 public static function get_event_mapper() {
202 self::init();
203 return self::$eventmapper;
204 }
84d865d6
RW
205
206 /**
207 * Return an event vault.
208 *
42e76c3f 209 * @return event_vault
84d865d6
RW
210 */
211 public static function get_event_vault() {
212 self::init();
213 return self::$eventvault;
214 }
c91b4c02
MN
215
216 /**
217 * Initialises the callbacks.
c28f1077
CB
218 *
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
221 * detail.
c91b4c02
MN
222 */
223 private static function initcallbacks() {
224 self::$callbacks = array(
225 'testing' => array(
c28f1077 226 // Always return an action event.
c91b4c02 227 'action' => function (event_interface $event) {
92ac08bc
CB
228 return new action_event(
229 $event,
c91b4c02
MN
230 new \core_calendar\local\event\value_objects\action(
231 'test',
232 new \moodle_url('http://example.com'),
233 420,
234 true
235 ));
236 },
c28f1077 237 // Always be visible.
c91b4c02
MN
238 'visibility' => function (event_interface $event) {
239 return true;
240 }
241 ),
242 'production' => array(
c28f1077
CB
243 // This function has type event_interface -> event_interface.
244 // This is enforced by the event_factory.
c91b4c02 245 'action' => function (event_interface $event) {
c28f1077
CB
246 // Callbacks will get supplied a "legacy" version
247 // of the event class.
c91b4c02
MN
248 $mapper = self::$eventmapper;
249 $action = component_callback(
250 'mod_' . $event->get_course_module()->get('modname'),
251 'core_calendar_provide_event_action',
252 [
253 $mapper->from_event_to_legacy_event($event),
254 self::$actionfactory
255 ]
256 );
257
c28f1077
CB
258 // If we get an action back, return an action event, otherwise
259 // continue piping through the original event.
260 //
261 // If a module does not implement the callback, component_callback
262 // returns null.
92ac08bc 263 return $action ? new action_event($event, $action) : $event;
c91b4c02 264 },
c28f1077
CB
265 // This function has type event_interface -> bool.
266 // This is enforced by the event_factory.
c91b4c02
MN
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',
272 [
273 $mapper->from_event_to_legacy_event($event)
274 ]
275 );
276
56885618
MN
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) {
279 return false;
280 }
281
c91b4c02
MN
282 // Module does not implement the callback, event should be visible.
283 if (is_null($eventvisible)) {
284 return true;
285 }
286
287 return $eventvisible ? true : false;
288 }
289 ),
290 );
291 }
5ca71c2d 292}