See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 39 and 401]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body