MDL-58518 calendar: ignore events from courses user not enrolled in
[moodle.git] / calendar / classes / local / event / container.php
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/>.
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  */
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.
44  *
45  * @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
46  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
47  */
48 class container {
49     /**
50      * @var event_factory $eventfactory Event factory.
51      */
52     protected static $eventfactory;
54     /**
55      * @var event_mapper $eventmapper Event mapper.
56      */
57     protected static $eventmapper;
59     /**
60      * @var action_factory $actionfactory Action factory.
61      */
62     protected static $actionfactory;
64     /**
65      * @var event_vault $eventvault Event vault.
66      */
67     protected static $eventvault;
69     /**
70      * @var raw_event_retrieval_strategy $eventretrievalstrategy Event retrieval strategy.
71      */
72     protected static $eventretrievalstrategy;
74     /**
75      * @var array A list of callbacks to use.
76      */
77     protected static $callbacks = array();
79     /**
80      * @var \stdClass[] An array of cached courses to use with the event factory.
81      */
82     protected static $coursecache = array();
84     /**
85      * @var \stdClass[] An array of cached modules to use with the event factory.
86      */
87     protected static $modulecache = array();
89     /**
90      * Initialises the dependency graph if it hasn't yet been.
91      */
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];
101             };
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.
110                 //
111                 // So we make a new even factory that doesn't do anyting other than
112                 // return the instance.
113                 new event_factory(
114                     // Never apply actions, simply return.
115                     function(event_interface $event) {
116                         return $event;
117                     },
118                     // Never hide an event.
119                     function() {
120                         return true;
121                     },
122                     // Never bail out early when instantiating an event.
123                     function() {
124                         return false;
125                     },
126                     self::$coursecache,
127                     self::$modulecache
128                 )
129             );
131             self::$eventfactory = new event_factory(
132                 $getcallback('action'),
133                 $getcallback('visibility'),
134                 function ($dbrow) {
135                     // At present we only handle callbacks in course modules.
136                     if (empty($dbrow->modulename)) {
137                         return false;
138                     }
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])) {
144                         return true;
145                     }
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) {
160                         return true;
161                     }
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);
171                     }
173                     return false;
174                 },
175                 self::$coursecache,
176                 self::$modulecache
177             );
178         }
180         if (empty(self::$eventvault)) {
181             self::$eventretrievalstrategy = new raw_event_retrieval_strategy();
182             self::$eventvault = new event_vault(self::$eventfactory, self::$eventretrievalstrategy);
183         }
184     }
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     }
196     /**
197      * Gets the event mapper.
198      *
199      * @return event_mapper
200      */
201     public static function get_event_mapper() {
202         self::init();
203         return self::$eventmapper;
204     }
206     /**
207      * Return an event vault.
208      *
209      * @return event_vault
210      */
211     public static function get_event_vault() {
212         self::init();
213         return self::$eventvault;
214     }
216     /**
217      * Initialises the callbacks.
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.
222      */
223     private static function initcallbacks() {
224         self::$callbacks = array(
225             'testing' => array(
226                 // Always return an action event.
227                 'action' => function (event_interface $event) {
228                     return new action_event(
229                         $event,
230                         new \core_calendar\local\event\value_objects\action(
231                             'test',
232                             new \moodle_url('http://example.com'),
233                             420,
234                             true
235                         ));
236                 },
237                 // Always be visible.
238                 'visibility' => function (event_interface $event) {
239                     return true;
240                 }
241             ),
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',
252                         [
253                             $mapper->from_event_to_legacy_event($event),
254                             self::$actionfactory
255                         ]
256                     );
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.
263                     return $action ? new action_event($event, $action) : $event;
264                 },
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',
272                         [
273                             $mapper->from_event_to_legacy_event($event)
274                         ]
275                     );
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                     }
282                     // Module does not implement the callback, event should be visible.
283                     if (is_null($eventvisible)) {
284                         return true;
285                     }
287                     return $eventvisible ? true : false;
288                 }
289             ),
290         );
291     }