bb88a5ca6dc99f4ca811c80e3580d6d9d73c3ecb
[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                     if (!$cm->uservisible) {
151                         return true;
152                     }
154                     // Ok, now check if we are looking at a completion event.
155                     if ($dbrow->eventtype === \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED) {
156                         // Need to have completion enabled before displaying these events.
157                         $course = new \stdClass();
158                         $course->id = $dbrow->courseid;
159                         $completion = new \completion_info($course);
161                         return (bool) !$completion->is_enabled($cm);
162                     }
164                     return false;
165                 },
166                 self::$coursecache,
167                 self::$modulecache
168             );
169         }
171         if (empty(self::$eventvault)) {
172             self::$eventretrievalstrategy = new raw_event_retrieval_strategy();
173             self::$eventvault = new event_vault(self::$eventfactory, self::$eventretrievalstrategy);
174         }
175     }
177     /**
178      * Gets the event factory.
179      *
180      * @return event_factory
181      */
182     public static function get_event_factory() {
183         self::init();
184         return self::$eventfactory;
185     }
187     /**
188      * Gets the event mapper.
189      *
190      * @return event_mapper
191      */
192     public static function get_event_mapper() {
193         self::init();
194         return self::$eventmapper;
195     }
197     /**
198      * Return an event vault.
199      *
200      * @return event_vault
201      */
202     public static function get_event_vault() {
203         self::init();
204         return self::$eventvault;
205     }
207     /**
208      * Initialises the callbacks.
209      *
210      * There are two sets here, one is used during PHPUnit runs.
211      * See the comment at the start of the init method for more
212      * detail.
213      */
214     private static function initcallbacks() {
215         self::$callbacks = array(
216             'testing' => array(
217                 // Always return an action event.
218                 'action' => function (event_interface $event) {
219                     return new action_event(
220                         $event,
221                         new \core_calendar\local\event\value_objects\action(
222                             'test',
223                             new \moodle_url('http://example.com'),
224                             420,
225                             true
226                         ));
227                 },
228                 // Always be visible.
229                 'visibility' => function (event_interface $event) {
230                     return true;
231                 }
232             ),
233             'production' => array(
234                 // This function has type event_interface -> event_interface.
235                 // This is enforced by the event_factory.
236                 'action' => function (event_interface $event) {
237                     // Callbacks will get supplied a "legacy" version
238                     // of the event class.
239                     $mapper = self::$eventmapper;
240                     $action = component_callback(
241                         'mod_' . $event->get_course_module()->get('modname'),
242                         'core_calendar_provide_event_action',
243                         [
244                             $mapper->from_event_to_legacy_event($event),
245                             self::$actionfactory
246                         ]
247                     );
249                     // If we get an action back, return an action event, otherwise
250                     // continue piping through the original event.
251                     //
252                     // If a module does not implement the callback, component_callback
253                     // returns null.
254                     return $action ? new action_event($event, $action) : $event;
255                 },
256                 // This function has type event_interface -> bool.
257                 // This is enforced by the event_factory.
258                 'visibility' => function (event_interface $event) {
259                     $mapper = self::$eventmapper;
260                     $eventvisible = component_callback(
261                         'mod_' . $event->get_course_module()->get('modname'),
262                         'core_calendar_is_event_visible',
263                         [
264                             $mapper->from_event_to_legacy_event($event)
265                         ]
266                     );
268                     // Do not display the event if there is nothing to action.
269                     if ($event instanceof action_event_interface && $event->get_action()->get_item_count() === 0) {
270                         return false;
271                     }
273                     // Module does not implement the callback, event should be visible.
274                     if (is_null($eventvisible)) {
275                         return true;
276                     }
278                     return $eventvisible ? true : false;
279                 }
280             ),
281         );
282     }