| 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 | }
 |