Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 
29
namespace core_calendar\local\event;
30
 
31
defined('MOODLE_INTERNAL') || die();
32
 
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;
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
 */
48
class container {
49
    /**
50
     * @var event_factory $eventfactory Event factory.
51
     */
52
    protected static $eventfactory;
53
 
54
    /**
55
     * @var event_mapper $eventmapper Event mapper.
56
     */
57
    protected static $eventmapper;
58
 
59
    /**
60
     * @var action_factory $actionfactory Action factory.
61
     */
62
    protected static $actionfactory;
63
 
64
    /**
65
     * @var event_vault $eventvault Event vault.
66
     */
67
    protected static $eventvault;
68
 
69
    /**
70
     * @var raw_event_retrieval_strategy $eventretrievalstrategy Event retrieval strategy.
71
     */
72
    protected static $eventretrievalstrategy;
73
 
74
    /**
75
     * @var \stdClass[] An array of cached courses to use with the event factory.
76
     */
77
    protected static $coursecache = array();
78
 
79
    /**
80
     * @var \stdClass[] An array of cached modules to use with the event factory.
81
     */
82
    protected static $modulecache = array();
83
 
84
    /**
85
     * @var int The requesting user. All capability checks are done against this user.
86
     */
87
    protected static $requestinguserid;
88
 
89
    /**
90
     * Initialises the dependency graph if it hasn't yet been.
91
     */
92
    private static function init() {
93
        if (empty(self::$eventfactory)) {
94
            self::$actionfactory = new action_factory();
95
            self::$eventmapper = new event_mapper(
96
                // The event mapper we return from here needs to know how to
97
                // make events, so it needs an event factory. However we can't
98
                // give it the same one as we store and return in the container
99
                // as that one uses all our plumbing to control event visibility.
100
                //
101
                // So we make a new even factory that doesn't do anyting other than
102
                // return the instance.
103
                new event_factory(
104
                    // Never apply actions, simply return.
105
                    function(event_interface $event) {
106
                        return $event;
107
                    },
108
                    // Never hide an event.
109
                    function() {
110
                        return true;
111
                    },
112
                    // Never bail out early when instantiating an event.
113
                    function() {
114
                        return false;
115
                    },
116
                    self::$coursecache,
117
                    self::$modulecache
118
                )
119
            );
120
 
121
            self::$eventfactory = new event_factory(
122
                [self::class, 'apply_component_provide_event_action'],
123
                [self::class, 'apply_component_is_event_visible'],
124
                function ($dbrow) {
125
                    $requestinguserid = self::get_requesting_user();
126
 
127
                    if (!empty($dbrow->categoryid)) {
128
                        // This is a category event. Check that the category is visible to this user.
129
                        $category = \core_course_category::get($dbrow->categoryid, IGNORE_MISSING, true, $requestinguserid);
130
 
131
                        if (empty($category) || !$category->is_uservisible($requestinguserid)) {
132
                            return true;
133
                        }
134
                    }
135
 
136
                    // For non-module events we assume that all checks were done in core_calendar_is_event_visible callback.
137
                    // For module events we also check that the course module and course itself are visible to the user.
138
                    if (empty($dbrow->modulename)) {
139
                        return false;
140
                    }
141
 
142
                    $instances = get_fast_modinfo($dbrow->courseid, $requestinguserid)->instances;
143
 
144
                    // If modinfo doesn't know about the module, we should ignore it.
145
                    if (!isset($instances[$dbrow->modulename]) || !isset($instances[$dbrow->modulename][$dbrow->instance])) {
146
                        return true;
147
                    }
148
 
149
                    $cm = $instances[$dbrow->modulename][$dbrow->instance];
150
 
151
                    // If the module is not visible to the current user, we should ignore it.
152
                    // We have to check enrolment here as well because the uservisible check
153
                    // looks for the "view" capability however some activities (such as Lesson)
154
                    // have that capability set on the "Authenticated User" role rather than
155
                    // on "Student" role, which means uservisible returns true even when the user
156
                    // is no longer enrolled in the course.
157
                    // So, with the following we are checking -
158
                    // 1) Only process modules if $cm->uservisible is true.
159
                    // 2) Only process modules for courses a user has the capability to view OR they are enrolled in.
160
                    // 3) Only process modules for courses that are visible OR if the course is not visible, the user
161
                    //    has the capability to view hidden courses.
162
                    if (!$cm->uservisible) {
163
                        return true;
164
                    }
165
 
166
                    $coursecontext = \context_course::instance($dbrow->courseid);
167
                    if (!$cm->get_course()->visible &&
168
                            !has_capability('moodle/course:viewhiddencourses', $coursecontext, $requestinguserid)) {
169
                        return true;
170
                    }
171
 
172
                    if (!has_capability('moodle/course:view', $coursecontext, $requestinguserid) &&
173
                            !is_enrolled($coursecontext, $requestinguserid)) {
174
                        return true;
175
                    }
176
 
177
                    // Ok, now check if we are looking at a completion event.
178
                    if ($dbrow->eventtype === \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED) {
179
                        // Need to have completion enabled before displaying these events.
180
                        $course = new \stdClass();
181
                        $course->id = $dbrow->courseid;
182
                        $completion = new \completion_info($course);
183
                        if ($completion->is_enabled($cm)) {
184
                            // Check if the event is completed, then in this case we do not need to complete it.
185
                            // Make sure we're using a cm_info object.
186
                            $completiondata = $completion->get_data($cm);
187
                            return intval($completiondata->completionstate) === COMPLETION_COMPLETE;
188
                        }
189
                        return true;
190
                    }
191
 
192
                    return false;
193
                },
194
                self::$coursecache,
195
                self::$modulecache
196
            );
197
        }
198
 
199
        if (empty(self::$eventvault)) {
200
            self::$eventretrievalstrategy = new raw_event_retrieval_strategy();
201
            self::$eventvault = new event_vault(self::$eventfactory, self::$eventretrievalstrategy);
202
        }
203
    }
204
 
205
    /**
206
     * Reset all static caches, called between tests.
207
     */
208
    public static function reset_caches() {
209
        self::$requestinguserid = null;
210
        self::$eventfactory = null;
211
        self::$eventmapper = null;
212
        self::$eventvault = null;
213
        self::$actionfactory = null;
214
        self::$eventretrievalstrategy = null;
215
        self::$coursecache = [];
216
        self::$modulecache = [];
217
    }
218
 
219
    /**
220
     * Gets the event factory.
221
     *
222
     * @return event_factory
223
     */
224
    public static function get_event_factory() {
225
        self::init();
226
        return self::$eventfactory;
227
    }
228
 
229
    /**
230
     * Gets the event mapper.
231
     *
232
     * @return event_mapper
233
     */
234
    public static function get_event_mapper() {
235
        self::init();
236
        return self::$eventmapper;
237
    }
238
 
239
    /**
240
     * Return an event vault.
241
     *
242
     * @return event_vault
243
     */
244
    public static function get_event_vault() {
245
        self::init();
246
        return self::$eventvault;
247
    }
248
 
249
    /**
250
     * Sets the requesting user so that all capability checks are done against this user.
251
     * Setting the requesting user (hence calling this function) is optional and if you do not so,
252
     * $USER will be used as the requesting user. However, if you wish to set the requesting user yourself,
253
     * you should call this function before any other function of the container class is called.
254
     *
255
     * @param int $userid The user id.
256
     * @throws \coding_exception
257
     */
258
    public static function set_requesting_user($userid) {
259
        self::$requestinguserid = $userid;
260
    }
261
 
262
    /**
263
     * Returns the requesting user id.
264
     * It usually is the current user unless it has been set explicitly using set_requesting_user.
265
     *
266
     * @return int
267
     */
268
    public static function get_requesting_user() {
269
        global $USER;
270
 
271
        return empty(self::$requestinguserid) ? $USER->id : self::$requestinguserid;
272
    }
273
 
274
    /**
275
     * Calls callback 'core_calendar_provide_event_action' from the component responsible for the event
276
     *
277
     * If no callback is present or callback returns null, there is no action on the event
278
     * and it will not be displayed on the dashboard.
279
     *
280
     * @param event_interface $event
281
     * @return action_event|event_interface
282
     */
283
    public static function apply_component_provide_event_action(event_interface $event) {
284
        // Callbacks will get supplied a "legacy" version
285
        // of the event class.
286
        $mapper = self::$eventmapper;
287
        $action = null;
288
        if ($event->get_component()) {
289
            $requestinguserid = self::get_requesting_user();
290
            $legacyevent = $mapper->from_event_to_legacy_event($event);
291
            // We know for a fact that the the requesting user might be different from the logged in user,
292
            // but the event mapper is not aware of that.
293
            if (empty($event->user) && !empty($legacyevent->userid)) {
294
                $legacyevent->userid = $requestinguserid;
295
            }
296
 
297
            // Any other event will not be displayed on the dashboard.
298
            $action = component_callback(
299
                $event->get_component(),
300
                'core_calendar_provide_event_action',
301
                [
302
                    $legacyevent,
303
                    self::$actionfactory,
304
                    $requestinguserid
305
                ]
306
            );
307
        }
308
 
309
        // If we get an action back, return an action event, otherwise
310
        // continue piping through the original event.
311
        //
312
        // If a module does not implement the callback, component_callback
313
        // returns null.
314
        return $action ? new action_event($event, $action) : $event;
315
    }
316
 
317
    /**
318
     * Calls callback 'core_calendar_is_event_visible' from the component responsible for the event
319
     *
320
     * The visibility callback is optional, if not present it is assumed as visible.
321
     * If it is an actionable event but the get_item_count() returns 0 the visibility
322
     * is set to false.
323
     *
324
     * @param event_interface $event
325
     * @return bool
326
     */
327
    public static function apply_component_is_event_visible(event_interface $event) {
328
        $mapper = self::$eventmapper;
329
        $eventvisible = null;
330
        if ($event->get_component()) {
331
            $requestinguserid = self::get_requesting_user();
332
            $legacyevent = $mapper->from_event_to_legacy_event($event);
333
            // We know for a fact that the the requesting user might be different from the logged in user,
334
            // but the event mapper is not aware of that.
335
            if (empty($event->user) && !empty($legacyevent->userid)) {
336
                $legacyevent->userid = $requestinguserid;
337
            }
338
 
339
            $eventvisible = component_callback(
340
                $event->get_component(),
341
                'core_calendar_is_event_visible',
342
                [
343
                    $legacyevent,
344
                    $requestinguserid
345
                ]
346
            );
347
        }
348
 
349
        // Do not display the event if there is nothing to action.
350
        if ($event instanceof action_event_interface && $event->get_action()->get_item_count() === 0) {
351
            return false;
352
        }
353
 
354
        // Module does not implement the callback, event should be visible.
355
        if (is_null($eventvisible)) {
356
            return true;
357
        }
358
 
359
        return $eventvisible ? true : false;
360
    }
361
}