See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
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 184 return (bool) !$completion->is_enabled($cm); 185 } 186 187 return false; 188 }, 189 self::$coursecache, 190 self::$modulecache 191 ); 192 } 193 194 if (empty(self::$eventvault)) { 195 self::$eventretrievalstrategy = new raw_event_retrieval_strategy(); 196 self::$eventvault = new event_vault(self::$eventfactory, self::$eventretrievalstrategy); 197 } 198 } 199 200 /** 201 * Reset all static caches, called between tests. 202 */ 203 public static function reset_caches() { 204 self::$requestinguserid = null; 205 self::$eventfactory = null; 206 self::$eventmapper = null; 207 self::$eventvault = null; 208 self::$actionfactory = null; 209 self::$eventretrievalstrategy = null; 210 self::$coursecache = []; 211 self::$modulecache = []; 212 } 213 214 /** 215 * Gets the event factory. 216 * 217 * @return event_factory 218 */ 219 public static function get_event_factory() { 220 self::init(); 221 return self::$eventfactory; 222 } 223 224 /** 225 * Gets the event mapper. 226 * 227 * @return event_mapper 228 */ 229 public static function get_event_mapper() { 230 self::init(); 231 return self::$eventmapper; 232 } 233 234 /** 235 * Return an event vault. 236 * 237 * @return event_vault 238 */ 239 public static function get_event_vault() { 240 self::init(); 241 return self::$eventvault; 242 } 243 244 /** 245 * Sets the requesting user so that all capability checks are done against this user. 246 * Setting the requesting user (hence calling this function) is optional and if you do not so, 247 * $USER will be used as the requesting user. However, if you wish to set the requesting user yourself, 248 * you should call this function before any other function of the container class is called. 249 * 250 * @param int $userid The user id. 251 * @throws \coding_exception 252 */ 253 public static function set_requesting_user($userid) { 254 self::$requestinguserid = $userid; 255 } 256 257 /** 258 * Returns the requesting user id. 259 * It usually is the current user unless it has been set explicitly using set_requesting_user. 260 * 261 * @return int 262 */ 263 public static function get_requesting_user() { 264 global $USER; 265 266 return empty(self::$requestinguserid) ? $USER->id : self::$requestinguserid; 267 } 268 269 /** 270 * Calls callback 'core_calendar_provide_event_action' from the component responsible for the event 271 * 272 * If no callback is present or callback returns null, there is no action on the event 273 * and it will not be displayed on the dashboard. 274 * 275 * @param event_interface $event 276 * @return action_event|event_interface 277 */ 278 public static function apply_component_provide_event_action(event_interface $event) { 279 // Callbacks will get supplied a "legacy" version 280 // of the event class. 281 $mapper = self::$eventmapper; 282 $action = null; 283 if ($event->get_component()) { 284 $requestinguserid = self::get_requesting_user(); 285 $legacyevent = $mapper->from_event_to_legacy_event($event); 286 // We know for a fact that the the requesting user might be different from the logged in user, 287 // but the event mapper is not aware of that. 288 if (empty($event->user) && !empty($legacyevent->userid)) { 289 $legacyevent->userid = $requestinguserid; 290 } 291 292 // Any other event will not be displayed on the dashboard. 293 $action = component_callback( 294 $event->get_component(), 295 'core_calendar_provide_event_action', 296 [ 297 $legacyevent, 298 self::$actionfactory, 299 $requestinguserid 300 ] 301 ); 302 } 303 304 // If we get an action back, return an action event, otherwise 305 // continue piping through the original event. 306 // 307 // If a module does not implement the callback, component_callback 308 // returns null. 309 return $action ? new action_event($event, $action) : $event; 310 } 311 312 /** 313 * Calls callback 'core_calendar_is_event_visible' from the component responsible for the event 314 * 315 * The visibility callback is optional, if not present it is assumed as visible. 316 * If it is an actionable event but the get_item_count() returns 0 the visibility 317 * is set to false. 318 * 319 * @param event_interface $event 320 * @return bool 321 */ 322 public static function apply_component_is_event_visible(event_interface $event) { 323 $mapper = self::$eventmapper; 324 $eventvisible = null; 325 if ($event->get_component()) { 326 $requestinguserid = self::get_requesting_user(); 327 $legacyevent = $mapper->from_event_to_legacy_event($event); 328 // We know for a fact that the the requesting user might be different from the logged in user, 329 // but the event mapper is not aware of that. 330 if (empty($event->user) && !empty($legacyevent->userid)) { 331 $legacyevent->userid = $requestinguserid; 332 } 333 334 $eventvisible = component_callback( 335 $event->get_component(), 336 'core_calendar_is_event_visible', 337 [ 338 $legacyevent, 339 $requestinguserid 340 ] 341 ); 342 } 343 344 // Do not display the event if there is nothing to action. 345 if ($event instanceof action_event_interface && $event->get_action()->get_item_count() === 0) { 346 return false; 347 } 348 349 // Module does not implement the callback, event should be visible. 350 if (is_null($eventvisible)) { 351 return true; 352 } 353 354 return $eventvisible ? true : false; 355 } 356 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body