first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+338
View File
@@ -0,0 +1,338 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains class containing the internal calendar API.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\container;
use core_calendar\local\event\entities\event_interface;
use core_calendar\local\event\exceptions\limit_invalid_parameter_exception;
/**
* Class containing the local calendar API.
*
* This should not be used outside of core_calendar.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class api {
/**
* Get all events restricted by various parameters, taking in to account user and group overrides.
*
* @param int|null $timestartfrom Events with timestart from this value (inclusive).
* @param int|null $timestartto Events with timestart until this value (inclusive).
* @param int|null $timesortfrom Events with timesort from this value (inclusive).
* @param int|null $timesortto Events with timesort until this value (inclusive).
* @param int|null $timestartaftereventid Restrict the events in the timestart range to ones after this ID.
* @param int|null $timesortaftereventid Restrict the events in the timesort range to ones after this ID.
* @param int $limitnum Return at most this number of events.
* @param int|null $type Return only events of this type.
* @param array|null $usersfilter Return only events for these users.
* @param array|null $groupsfilter Return only events for these groups.
* @param array|null $coursesfilter Return only events for these courses.
* @param bool $withduration If true return only events starting within specified
* timestart otherwise return in progress events as well.
* @param bool $ignorehidden If true don't return hidden events.
* @return \core_calendar\local\event\entities\event_interface[] Array of event_interfaces.
*/
public static function get_events(
$timestartfrom = null,
$timestartto = null,
$timesortfrom = null,
$timesortto = null,
$timestartaftereventid = null,
$timesortaftereventid = null,
$limitnum = 20,
$type = null,
array $usersfilter = null,
array $groupsfilter = null,
array $coursesfilter = null,
array $categoriesfilter = null,
$withduration = true,
$ignorehidden = true,
callable $filter = null
) {
global $USER;
$vault = \core_calendar\local\event\container::get_event_vault();
$timestartafterevent = null;
$timesortafterevent = null;
if ($timestartaftereventid && $event = $vault->get_event_by_id($timestartaftereventid)) {
$timestartafterevent = $event;
}
if ($timesortaftereventid && $event = $vault->get_event_by_id($timesortaftereventid)) {
$timesortafterevent = $event;
}
return $vault->get_events(
$timestartfrom,
$timestartto,
$timesortfrom,
$timesortto,
$timestartafterevent,
$timesortafterevent,
$limitnum,
$type,
$usersfilter,
$groupsfilter,
$coursesfilter,
$categoriesfilter,
$withduration,
$ignorehidden,
$filter
);
}
/**
* Get a list of action events for the logged in user by the given
* timesort values.
*
* @param int|null $timesortfrom The start timesort value (inclusive)
* @param int|null $timesortto The end timesort value (inclusive)
* @param int|null $aftereventid Only return events after this one
* @param int $limitnum Limit results to this amount (between 1 and 50)
* @param bool $lmittononsuspendedevents Limit course events to courses the user is active in (not suspended).
* @param \stdClass|null $user The user id or false for $USER
* @param string|null $searchvalue The value a user wishes to search against
* @return array A list of action_event_interface objects
* @throws \moodle_exception
*/
public static function get_action_events_by_timesort(
$timesortfrom = null,
$timesortto = null,
$aftereventid = null,
$limitnum = 20,
$limittononsuspendedevents = false,
?\stdClass $user = null,
?string $searchvalue = null
) {
global $USER;
if (!$user) {
$user = $USER;
}
if (is_null($timesortfrom) && is_null($timesortto)) {
throw new \moodle_exception("Must provide a timesort to and/or from value");
}
if ($limitnum < 1 || $limitnum > 50) {
throw new \moodle_exception("Limit must be between 1 and 50 (inclusive)");
}
\core_calendar\local\event\container::set_requesting_user($user->id);
$vault = \core_calendar\local\event\container::get_event_vault();
$afterevent = null;
if ($aftereventid && $event = $vault->get_event_by_id($aftereventid)) {
$afterevent = $event;
}
return $vault->get_action_events_by_timesort($user, $timesortfrom, $timesortto, $afterevent, $limitnum,
$limittononsuspendedevents, $searchvalue);
}
/**
* Get a list of action events for the logged in user by the given
* course and timesort values.
*
* @param \stdClass $course The course the events must belong to
* @param int|null $timesortfrom The start timesort value (inclusive)
* @param int|null $timesortto The end timesort value (inclusive)
* @param int|null $aftereventid Only return events after this one
* @param int $limitnum Limit results to this amount (between 1 and 50)
* @param string|null $searchvalue The value a user wishes to search against
* @return array A list of action_event_interface objects
* @throws limit_invalid_parameter_exception
*/
public static function get_action_events_by_course(
$course,
$timesortfrom = null,
$timesortto = null,
$aftereventid = null,
$limitnum = 20,
?string $searchvalue = null
) {
global $USER;
if ($limitnum < 1 || $limitnum > 50) {
throw new limit_invalid_parameter_exception(
"Limit must be between 1 and 50 (inclusive)");
}
$vault = \core_calendar\local\event\container::get_event_vault();
$afterevent = null;
if ($aftereventid && $event = $vault->get_event_by_id($aftereventid)) {
$afterevent = $event;
}
return $vault->get_action_events_by_course(
$USER, $course, $timesortfrom, $timesortto, $afterevent, $limitnum, $searchvalue);
}
/**
* Get a list of action events for the logged in user by the given
* courses and timesort values.
*
* The limit number applies per course, not for the result set as a whole.
* E.g. Requesting 3 courses with a limit of 10 will result in up to 30
* events being returned (up to 10 per course).
*
* @param array $courses The courses the events must belong to
* @param int|null $timesortfrom The start timesort value (inclusive)
* @param int|null $timesortto The end timesort value (inclusive)
* @param int $limitnum Limit results per course to this amount (between 1 and 50)
* @param string|null $searchvalue The value a user wishes to search against
* @return array A list of action_event_interface objects indexed by course id
*/
public static function get_action_events_by_courses(
$courses = [],
$timesortfrom = null,
$timesortto = null,
$limitnum = 20,
?string $searchvalue = null
) {
$return = [];
foreach ($courses as $course) {
$return[$course->id] = self::get_action_events_by_course(
$course,
$timesortfrom,
$timesortto,
null,
$limitnum,
$searchvalue
);
}
return $return;
}
/**
* Change the start day for an event. Only the date will be
* modified, the time of day for the event will be left as is.
*
* @param event_interface $event The existing event to modify
* @param DateTimeInterface $startdate The new date to use for the start day
* @return event_interface The new event with updated start date
*/
public static function update_event_start_day(
event_interface $event,
\DateTimeInterface $startdate
) {
global $DB;
$mapper = container::get_event_mapper();
$legacyevent = $mapper->from_event_to_legacy_event($event);
$hascoursemodule = !empty($event->get_course_module());
$moduleinstance = null;
$starttime = $event->get_times()->get_start_time()->setDate(
$startdate->format('Y'),
$startdate->format('n'),
$startdate->format('j')
);
$starttimestamp = $starttime->getTimestamp();
if ($hascoursemodule) {
$moduleinstance = $DB->get_record(
$event->get_course_module()->get('modname'),
['id' => $event->get_course_module()->get('instance')],
'*',
MUST_EXIST
);
// If there is a timestart range callback implemented then we can
// use the values returned from the valid timestart range to apply
// some default validation on the event's timestart value to ensure
// that it falls within the specified range.
list($min, $max) = component_callback(
'mod_' . $event->get_course_module()->get('modname'),
'core_calendar_get_valid_event_timestart_range',
[$legacyevent, $moduleinstance],
[false, false]
);
} else if ($legacyevent->courseid != 0 && $legacyevent->courseid != SITEID && $legacyevent->groupid == 0) {
// This is a course event.
list($min, $max) = component_callback(
'core_course',
'core_calendar_get_valid_event_timestart_range',
[$legacyevent, $event->get_course()->get_proxied_instance()],
[0, 0]
);
} else {
$min = $max = 0;
}
// If the callback returns false for either value it means that
// there is no valid time start range.
if ($min === false || $max === false) {
throw new \moodle_exception('The start day of this event can not be modified');
}
if ($min && $starttimestamp < $min[0]) {
throw new \moodle_exception($min[1]);
}
if ($max && $starttimestamp > $max[0]) {
throw new \moodle_exception($max[1]);
}
// This function does our capability checks.
$legacyevent->update((object) ['timestart' => $starttime->getTimestamp()]);
// Check that the user is allowed to manually edit calendar events before
// calling the event updated callback. The manual flag causes the code to
// check the user has the capabilities to modify the modules.
//
// We don't want to call the event update callback if the user isn't allowed
// to modify course modules because depending on the callback it can make
// some changes that would be considered security issues, such as updating the
// due date for an assignment.
if ($hascoursemodule && calendar_edit_event_allowed($legacyevent, true)) {
// If this event is from an activity then we need to call
// the activity callback to let it know that the event it
// created has been modified so it needs to update accordingly.
component_callback(
'mod_' . $event->get_course_module()->get('modname'),
'core_calendar_event_timestart_updated',
[$legacyevent, $moduleinstance]
);
// Rebuild the course cache to make sure the updated dates are reflected.
$courseid = $event->get_course()->get('id');
$cmid = $event->get_course_module()->get('id');
\course_modinfo::purge_course_module_cache($courseid, $cmid);
rebuild_course_cache($courseid, true, true);
}
return $mapper->from_legacy_event_to_event($legacyevent);
}
}
+361
View File
@@ -0,0 +1,361 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Core container for calendar events.
*
* The purpose of this class is simply to wire together the various
* implementations of calendar event components to produce a solution
* to the problems Moodle core wants to solve.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event;
defined('MOODLE_INTERNAL') || die();
use core_calendar\action_factory;
use core_calendar\local\event\data_access\event_vault;
use core_calendar\local\event\entities\action_event;
use core_calendar\local\event\entities\action_event_interface;
use core_calendar\local\event\entities\event_interface;
use core_calendar\local\event\factories\event_factory;
use core_calendar\local\event\mappers\event_mapper;
use core_calendar\local\event\strategies\raw_event_retrieval_strategy;
/**
* Core container.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class container {
/**
* @var event_factory $eventfactory Event factory.
*/
protected static $eventfactory;
/**
* @var event_mapper $eventmapper Event mapper.
*/
protected static $eventmapper;
/**
* @var action_factory $actionfactory Action factory.
*/
protected static $actionfactory;
/**
* @var event_vault $eventvault Event vault.
*/
protected static $eventvault;
/**
* @var raw_event_retrieval_strategy $eventretrievalstrategy Event retrieval strategy.
*/
protected static $eventretrievalstrategy;
/**
* @var \stdClass[] An array of cached courses to use with the event factory.
*/
protected static $coursecache = array();
/**
* @var \stdClass[] An array of cached modules to use with the event factory.
*/
protected static $modulecache = array();
/**
* @var int The requesting user. All capability checks are done against this user.
*/
protected static $requestinguserid;
/**
* Initialises the dependency graph if it hasn't yet been.
*/
private static function init() {
if (empty(self::$eventfactory)) {
self::$actionfactory = new action_factory();
self::$eventmapper = new event_mapper(
// The event mapper we return from here needs to know how to
// make events, so it needs an event factory. However we can't
// give it the same one as we store and return in the container
// as that one uses all our plumbing to control event visibility.
//
// So we make a new even factory that doesn't do anyting other than
// return the instance.
new event_factory(
// Never apply actions, simply return.
function(event_interface $event) {
return $event;
},
// Never hide an event.
function() {
return true;
},
// Never bail out early when instantiating an event.
function() {
return false;
},
self::$coursecache,
self::$modulecache
)
);
self::$eventfactory = new event_factory(
[self::class, 'apply_component_provide_event_action'],
[self::class, 'apply_component_is_event_visible'],
function ($dbrow) {
$requestinguserid = self::get_requesting_user();
if (!empty($dbrow->categoryid)) {
// This is a category event. Check that the category is visible to this user.
$category = \core_course_category::get($dbrow->categoryid, IGNORE_MISSING, true, $requestinguserid);
if (empty($category) || !$category->is_uservisible($requestinguserid)) {
return true;
}
}
// For non-module events we assume that all checks were done in core_calendar_is_event_visible callback.
// For module events we also check that the course module and course itself are visible to the user.
if (empty($dbrow->modulename)) {
return false;
}
$instances = get_fast_modinfo($dbrow->courseid, $requestinguserid)->instances;
// If modinfo doesn't know about the module, we should ignore it.
if (!isset($instances[$dbrow->modulename]) || !isset($instances[$dbrow->modulename][$dbrow->instance])) {
return true;
}
$cm = $instances[$dbrow->modulename][$dbrow->instance];
// If the module is not visible to the current user, we should ignore it.
// We have to check enrolment here as well because the uservisible check
// looks for the "view" capability however some activities (such as Lesson)
// have that capability set on the "Authenticated User" role rather than
// on "Student" role, which means uservisible returns true even when the user
// is no longer enrolled in the course.
// So, with the following we are checking -
// 1) Only process modules if $cm->uservisible is true.
// 2) Only process modules for courses a user has the capability to view OR they are enrolled in.
// 3) Only process modules for courses that are visible OR if the course is not visible, the user
// has the capability to view hidden courses.
if (!$cm->uservisible) {
return true;
}
$coursecontext = \context_course::instance($dbrow->courseid);
if (!$cm->get_course()->visible &&
!has_capability('moodle/course:viewhiddencourses', $coursecontext, $requestinguserid)) {
return true;
}
if (!has_capability('moodle/course:view', $coursecontext, $requestinguserid) &&
!is_enrolled($coursecontext, $requestinguserid)) {
return true;
}
// Ok, now check if we are looking at a completion event.
if ($dbrow->eventtype === \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED) {
// Need to have completion enabled before displaying these events.
$course = new \stdClass();
$course->id = $dbrow->courseid;
$completion = new \completion_info($course);
if ($completion->is_enabled($cm)) {
// Check if the event is completed, then in this case we do not need to complete it.
// Make sure we're using a cm_info object.
$completiondata = $completion->get_data($cm);
return intval($completiondata->completionstate) === COMPLETION_COMPLETE;
}
return true;
}
return false;
},
self::$coursecache,
self::$modulecache
);
}
if (empty(self::$eventvault)) {
self::$eventretrievalstrategy = new raw_event_retrieval_strategy();
self::$eventvault = new event_vault(self::$eventfactory, self::$eventretrievalstrategy);
}
}
/**
* Reset all static caches, called between tests.
*/
public static function reset_caches() {
self::$requestinguserid = null;
self::$eventfactory = null;
self::$eventmapper = null;
self::$eventvault = null;
self::$actionfactory = null;
self::$eventretrievalstrategy = null;
self::$coursecache = [];
self::$modulecache = [];
}
/**
* Gets the event factory.
*
* @return event_factory
*/
public static function get_event_factory() {
self::init();
return self::$eventfactory;
}
/**
* Gets the event mapper.
*
* @return event_mapper
*/
public static function get_event_mapper() {
self::init();
return self::$eventmapper;
}
/**
* Return an event vault.
*
* @return event_vault
*/
public static function get_event_vault() {
self::init();
return self::$eventvault;
}
/**
* Sets the requesting user so that all capability checks are done against this user.
* Setting the requesting user (hence calling this function) is optional and if you do not so,
* $USER will be used as the requesting user. However, if you wish to set the requesting user yourself,
* you should call this function before any other function of the container class is called.
*
* @param int $userid The user id.
* @throws \coding_exception
*/
public static function set_requesting_user($userid) {
self::$requestinguserid = $userid;
}
/**
* Returns the requesting user id.
* It usually is the current user unless it has been set explicitly using set_requesting_user.
*
* @return int
*/
public static function get_requesting_user() {
global $USER;
return empty(self::$requestinguserid) ? $USER->id : self::$requestinguserid;
}
/**
* Calls callback 'core_calendar_provide_event_action' from the component responsible for the event
*
* If no callback is present or callback returns null, there is no action on the event
* and it will not be displayed on the dashboard.
*
* @param event_interface $event
* @return action_event|event_interface
*/
public static function apply_component_provide_event_action(event_interface $event) {
// Callbacks will get supplied a "legacy" version
// of the event class.
$mapper = self::$eventmapper;
$action = null;
if ($event->get_component()) {
$requestinguserid = self::get_requesting_user();
$legacyevent = $mapper->from_event_to_legacy_event($event);
// We know for a fact that the the requesting user might be different from the logged in user,
// but the event mapper is not aware of that.
if (empty($event->user) && !empty($legacyevent->userid)) {
$legacyevent->userid = $requestinguserid;
}
// Any other event will not be displayed on the dashboard.
$action = component_callback(
$event->get_component(),
'core_calendar_provide_event_action',
[
$legacyevent,
self::$actionfactory,
$requestinguserid
]
);
}
// If we get an action back, return an action event, otherwise
// continue piping through the original event.
//
// If a module does not implement the callback, component_callback
// returns null.
return $action ? new action_event($event, $action) : $event;
}
/**
* Calls callback 'core_calendar_is_event_visible' from the component responsible for the event
*
* The visibility callback is optional, if not present it is assumed as visible.
* If it is an actionable event but the get_item_count() returns 0 the visibility
* is set to false.
*
* @param event_interface $event
* @return bool
*/
public static function apply_component_is_event_visible(event_interface $event) {
$mapper = self::$eventmapper;
$eventvisible = null;
if ($event->get_component()) {
$requestinguserid = self::get_requesting_user();
$legacyevent = $mapper->from_event_to_legacy_event($event);
// We know for a fact that the the requesting user might be different from the logged in user,
// but the event mapper is not aware of that.
if (empty($event->user) && !empty($legacyevent->userid)) {
$legacyevent->userid = $requestinguserid;
}
$eventvisible = component_callback(
$event->get_component(),
'core_calendar_is_event_visible',
[
$legacyevent,
$requestinguserid
]
);
}
// Do not display the event if there is nothing to action.
if ($event instanceof action_event_interface && $event->get_action()->get_item_count() === 0) {
return false;
}
// Module does not implement the callback, event should be visible.
if (is_null($eventvisible)) {
return true;
}
return $eventvisible ? true : false;
}
}
@@ -0,0 +1,456 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Event vault class
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\data_access;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\entities\action_event_interface;
use core_calendar\local\event\entities\event_interface;
use core_calendar\local\event\exceptions\limit_invalid_parameter_exception;
use core_calendar\local\event\factories\action_factory_interface;
use core_calendar\local\event\factories\event_factory_interface;
use core_calendar\local\event\strategies\raw_event_retrieval_strategy_interface;
/**
* Event vault class.
*
* This class will handle interacting with the database layer to retrieve
* the records. This is required to house the complex logic required for
* pagination because it's not a one-to-one mapping between database records
* and users.
*
* This is a repository. It's called a vault to reduce confusion because
* Moodle has already taken the name repository. Vault is cooler anyway.
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event_vault implements event_vault_interface {
/**
* @var event_factory_interface $factory Factory for creating events.
*/
protected $factory;
/**
* @var raw_event_retrieval_strategy_interface $retrievalstrategy Strategy for getting events from the DB.
*/
protected $retrievalstrategy;
/**
* Create an event vault.
*
* @param event_factory_interface $factory An event factory
* @param raw_event_retrieval_strategy_interface $retrievalstrategy
*/
public function __construct(
event_factory_interface $factory,
raw_event_retrieval_strategy_interface $retrievalstrategy
) {
$this->factory = $factory;
$this->retrievalstrategy = $retrievalstrategy;
}
public function get_event_by_id($id) {
global $DB;
if ($record = $DB->get_record('event', ['id' => $id])) {
return $this->transform_from_database_record($record);
} else {
return false;
}
}
public function get_events(
$timestartfrom = null,
$timestartto = null,
$timesortfrom = null,
$timesortto = null,
event_interface $timestartafterevent = null,
event_interface $timesortafterevent = null,
$limitnum = 20,
$type = null,
array $usersfilter = null,
array $groupsfilter = null,
array $coursesfilter = null,
array $categoriesfilter = null,
$withduration = true,
$ignorehidden = true,
callable $filter = null,
?string $searchvalue = null
) {
$fromquery = function($field, $timefrom, $lastseenmethod, $afterevent, $withduration) {
if (!$timefrom) {
return false;
}
return $this->timefield_pagination_from(
$field,
$timefrom,
$afterevent ? $afterevent->get_times()->{$lastseenmethod}()->getTimestamp() : null,
$afterevent ? $afterevent->get_id() : null,
$withduration
);
};
$toquery = function($field, $timeto, $lastseenmethod, $afterevent) {
if (!$timeto) {
return false;
}
return $this->timefield_pagination_to(
$field,
$timeto,
$afterevent ? $afterevent->get_times()->{$lastseenmethod}()->getTimestamp() : null,
$afterevent ? $afterevent->get_id() : null
);
};
$timesortfromquery = $fromquery('timesort', $timesortfrom, 'get_sort_time', $timesortafterevent, $withduration);
$timesorttoquery = $toquery('timesort', $timesortto, 'get_sort_time', $timesortafterevent);
$timestartfromquery = $fromquery('timestart', $timestartfrom, 'get_start_time', $timestartafterevent, $withduration);
$timestarttoquery = $toquery('timestart', $timestartto, 'get_start_time', $timestartafterevent);
if (($timesortto && !$timesorttoquery) || ($timestartto && !$timestarttoquery)) {
return [];
}
$searchquery = $this->generate_search_subquery($searchvalue);
$params = array_merge(
$type ? ['type' => $type] : [],
$timesortfromquery ? $timesortfromquery['params'] : [],
$timesorttoquery ? $timesorttoquery['params'] : [],
$timestartfromquery ? $timestartfromquery['params'] : [],
$timestarttoquery ? $timestarttoquery['params'] : [],
$searchquery ? $searchquery['params'] : [],
);
$where = array_merge(
$type ? ['type = :type'] : [],
$timesortfromquery ? $timesortfromquery['where'] : [],
$timesorttoquery ? $timesorttoquery['where'] : [],
$timestartfromquery ? $timestartfromquery['where'] : [],
$timestarttoquery ? $timestarttoquery['where'] : [],
$searchquery ? [$searchquery['where']] : [],
);
$offset = 0;
$events = [];
while ($records = array_values($this->retrievalstrategy->get_raw_events(
$usersfilter,
$groupsfilter,
$coursesfilter,
$categoriesfilter,
$where,
$params,
"COALESCE(e.timesort, e.timestart) ASC, e.id ASC",
$offset,
$limitnum,
$ignorehidden
))) {
foreach ($records as $record) {
if ($event = $this->transform_from_database_record($record)) {
$filtertest = $filter ? $filter($event) : true;
if ($event && $filtertest) {
$events[] = $event;
}
if (count($events) == $limitnum) {
// We've got all of the events so break both loops.
break 2;
}
}
}
if (!$limitnum) {
break;
} else {
$offset += $limitnum;
}
}
return $events;
}
public function get_action_events_by_timesort(
\stdClass $user,
$timesortfrom = null,
$timesortto = null,
event_interface $afterevent = null,
$limitnum = 20,
$limittononsuspendedevents = false,
?string $searchvalue = null
) {
$courseids = array_map(function($course) {
return $course->id;
}, enrol_get_all_users_courses($user->id, $limittononsuspendedevents));
$groupids = array_reduce($courseids, function($carry, $courseid) use ($user) {
$groupings = groups_get_user_groups($courseid, $user->id);
// Grouping 0 is all groups.
return array_merge($carry, $groupings[0]);
}, []);
// Always include the site events.
$courseids = $courseids ? array_merge($courseids, [SITEID]) : $courseids;
return $this->get_events(
null,
null,
$timesortfrom,
$timesortto,
null,
$afterevent,
$limitnum,
CALENDAR_EVENT_TYPE_ACTION,
[$user->id],
$groupids ? $groupids : null,
$courseids ? $courseids : null,
null, // All categories.
true,
true,
function ($event) {
return $event instanceof action_event_interface;
},
$searchvalue
);
}
public function get_action_events_by_course(
\stdClass $user,
\stdClass $course,
$timesortfrom = null,
$timesortto = null,
event_interface $afterevent = null,
$limitnum = 20,
?string $searchvalue = null
) {
$groupings = groups_get_user_groups($course->id, $user->id);
return array_values(
$this->get_events(
null,
null,
$timesortfrom,
$timesortto,
null,
$afterevent,
$limitnum,
CALENDAR_EVENT_TYPE_ACTION,
[$user->id],
$groupings[0] ? $groupings[0] : null,
[$course->id],
[],
true,
true,
function ($event) use ($course) {
return $event instanceof action_event_interface && $event->get_course()->get('id') == $course->id;
},
$searchvalue
)
);
}
/**
* Generates SQL subquery and parameters for 'from' pagination.
*
* @param string $field
* @param int $timefrom
* @param int|null $lastseentime
* @param int|null $lastseenid
* @param bool $withduration
* @return array
*/
protected function timefield_pagination_from(
$field,
$timefrom,
$lastseentime = null,
$lastseenid = null,
$withduration = true
) {
$where = '';
$params = [];
if ($lastseentime && $lastseentime >= $timefrom) {
$where = '((timesort = :timefrom1 AND e.id > :timefromid) OR timesort > :timefrom2)';
if ($field === 'timestart') {
$where = '((timestart = :timefrom1 AND e.id > :timefromid) OR timestart > :timefrom2' .
($withduration ? ' OR timestart + timeduration > :timefrom3' : '') . ')';
}
$params['timefromid'] = $lastseenid;
$params['timefrom1'] = $lastseentime;
$params['timefrom2'] = $lastseentime;
$params['timefrom3'] = $lastseentime;
} else {
$where = 'timesort >= :timefrom';
if ($field === 'timestart') {
$where = '(timestart >= :timefrom' .
($withduration ? ' OR timestart + timeduration > :timefrom2' : '') . ')';
}
$params['timefrom'] = $timefrom;
$params['timefrom2'] = $timefrom;
}
return ['where' => [$where], 'params' => $params];
}
/**
* Generates SQL subquery and parameters for 'to' pagination.
*
* @param string $field
* @param int $timeto
* @param int|null $lastseentime
* @param int|null $lastseenid
* @return array|bool
*/
protected function timefield_pagination_to(
$field,
$timeto,
$lastseentime = null,
$lastseenid = null
) {
$where = [];
$params = [];
if ($lastseentime && $lastseentime > $timeto) {
// The last seen event from this set is after the time sort range which
// means all events in this range have been seen, so we can just return
// early here.
return false;
} else if ($lastseentime && $lastseentime == $timeto) {
$where[] = '((timesort = :timeto1 AND e.id > :timetoid) OR timesort < :timeto2)';
if ($field === 'timestart') {
$where[] = '((timestart = :timeto1 AND e.id > :timetoid) OR timestart < :timeto2)';
}
$params['timetoid'] = $lastseenid;
$params['timeto1'] = $timeto;
$params['timeto2'] = $timeto;
} else {
$where[] = ($field === 'timestart' ? 'timestart' : 'timesort') . ' <= :timeto';
$params['timeto'] = $timeto;
}
return ['where' => $where, 'params' => $params];
}
/**
* Create an event from a database record.
*
* @param \stdClass $record The database record
* @return event_interface|null
*/
protected function transform_from_database_record(\stdClass $record) {
return $this->factory->create_instance($record);
}
/**
* Fetches records from DB.
*
* @param int $userid
* @param array|null $whereconditions
* @param array $whereparams
* @param string $ordersql
* @param int $offset
* @param int $limitnum
* @return array
*/
protected function get_from_db(
$userid,
$whereconditions,
$whereparams,
$ordersql,
$offset,
$limitnum
) {
return array_values(
$this->retrievalstrategy->get_raw_events(
[$userid],
null,
null,
null,
$whereconditions,
$whereparams,
$ordersql,
$offset,
$limitnum
)
);
}
/**
* Generates SQL subquery and parameters for event searching.
*
* @param string|null $searchvalue Search value.
* @return array|null
*/
protected function generate_search_subquery(?string $searchvalue): ?array {
global $CFG, $DB;
if (!$searchvalue) {
return null;
}
$parts = preg_split('/\s+/', $searchvalue);
$wherecoursenameconditions = [];
$whereactivitynameconditions = [];
foreach ($parts as $index => $part) {
// Course name searching.
$wherecoursenameconditions[] = $DB->sql_like('c.fullname', ':cfullname' . $index, false);
$params['cfullname'. $index] = '%' . $DB->sql_like_escape($part) . '%';
// Activity name searching.
$whereactivitynameconditions[] = $DB->sql_like('e.name', ':eventname' . $index, false);
$params['eventname'. $index] = '%' . $DB->sql_like_escape($part) . '%';
}
// Activity type searching.
$whereconditions[] = $DB->sql_like('e.modulename', ':modulename', false);
$params['modulename'] = '%' . $DB->sql_like_escape($searchvalue) . '%';
// Activity type searching (localised type name).
require_once($CFG->dirroot . '/course/lib.php');
// Search in modules' singular and plural names.
$modules = array_keys(array_merge(
preg_grep('/' . $searchvalue . '/i', get_module_types_names()) ?: [],
preg_grep('/' . $searchvalue . '/i', get_module_types_names(true)) ?: [],
));
if ($modules) {
[$insql, $inparams] = $DB->get_in_or_equal($modules, SQL_PARAMS_NAMED, 'exactmodulename');
$whereconditions[] = 'e.modulename ' . $insql;
$params += $inparams;
}
$whereclause = '(';
$whereclause .= implode(' OR ', $whereconditions);
$whereclause .= ' OR (' . implode(' AND ', $wherecoursenameconditions) . ')';
$whereclause .= ' OR (' . implode(' AND ', $whereactivitynameconditions) . ')';
$whereclause .= ')';
return ['where' => $whereclause, 'params' => $params];
}
}
@@ -0,0 +1,137 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Event vault interface
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\data_access;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\entities\event_interface;
/**
* Interface for an event vault class
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface event_vault_interface {
/**
* Retrieve an event for the given id.
*
* @param int $id The event id
* @return event_interface|false
*/
public function get_event_by_id($id);
/**
* Get all events restricted by various parameters, taking in to account user and group overrides.
*
* @param int|null $timestartfrom Events with timestart from this value (inclusive).
* @param int|null $timestartto Events with timestart until this value (inclusive).
* @param int|null $timesortfrom Events with timesort from this value (inclusive).
* @param int|null $timesortto Events with timesort until this value (inclusive).
* @param event_interface|null $timestartafterevent Restrict the events in the timestart range to ones after this one.
* @param event_interface|null $timesortafterevent Restrict the events in the timesort range to ones after this one.
* @param int $limitnum Return at most this number of events.
* @param int|null $type Return only events of this type.
* @param array|null $usersfilter Return only events for these users.
* @param array|null $groupsfilter Return only events for these groups.
* @param array|null $coursesfilter Return only events for these courses.
* @param bool $withduration If true return only events starting within specified
* timestart otherwise return in progress events as well.
* @param bool $ignorehidden If true don't return hidden events.
* @param callable|null $filter Additional logic to filter out unwanted events.
* Must return true to keep the event, false to discard it.
* @param string|null $searchvalue The value a user wishes to search against
* @return event_interface[] Array of event_interfaces.
*/
public function get_events(
$timestartfrom = null,
$timestartto = null,
$timesortfrom = null,
$timesortto = null,
event_interface $timestartafterevent = null,
event_interface $timesortafterevent = null,
$limitnum = 20,
$type = null,
array $usersfilter = null,
array $groupsfilter = null,
array $coursesfilter = null,
array $categoriesfilter = null,
$withduration = true,
$ignorehidden = true,
callable $filter = null,
?string $searchvalue = null
);
/**
* Retrieve an array of events for the given user and time constraints.
*
* If using this function for pagination then you can provide the last event that you've seen
* ($afterevent) and it will be used to appropriately offset the result set so that you don't
* receive the same events again.
* @param \stdClass $user The user for whom the events belong
* @param int $timesortfrom Events with timesort from this value (inclusive)
* @param int $timesortto Events with timesort until this value (inclusive)
* @param event_interface $afterevent Only return events after this one
* @param int $limitnum Return at most this number of events
* @param bool $lmittononsuspendedevents Limit course events to courses the user is active in (not suspended).
* @param string|null $searchvalue The value a user wishes to search against
* @return event_interface
*/
public function get_action_events_by_timesort(
\stdClass $user,
$timesortfrom,
$timesortto,
event_interface $afterevent,
$limitnum,
$limittononsuspendedevents,
?string $searchvalue = null
);
/**
* Retrieve an array of events for the given user filtered by the course and time constraints.
*
* If using this function for pagination then you can provide the last event that you've seen
* ($afterevent) and it will be used to appropriately offset the result set so that you don't
* receive the same events again.
*
* @param \stdClass $user The user for whom the events belong
* @param \stdClass $course The course to filter by
* @param int $timesortfrom Events with timesort from this value (inclusive)
* @param int $timesortto Events with timesort until this value (inclusive)
* @param event_interface $afterevent Only return events after this one
* @param int $limitnum Return at most this number of events
* @param string|null $searchvalue The value a user wishes to search against
* @return action_event_interface
*/
public function get_action_events_by_course(
\stdClass $user,
\stdClass $course,
$timesortfrom,
$timesortto,
event_interface $afterevent,
$limitnum,
?string $searchvalue = null
);
}
@@ -0,0 +1,136 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Calendar action event class.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\entities;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\factories\action_factory_interface;
/**
* Class representing an actionable event.
*
* An actionable event can be thought of as an embellished event. That is,
* it does everything a regular event does, but has some extra information
* attached to it. For example, the URL a user needs to visit to complete
* an action, the number of actionable items, etc.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class action_event implements action_event_interface {
/**
* @var event_interface $event The event to delegate to.
*/
protected $event;
/**
* @var action_interface $action The action associated with this event.
*/
protected $action;
/**
* @var proxy_interface $category Category for this event.
*/
protected $category;
/**
* Constructor.
*
* @param event_interface $event The event to delegate to.
* @param action_interface $action The action associated with this event.
*/
public function __construct(event_interface $event, action_interface $action) {
$this->event = $event;
$this->action = $action;
}
public function get_id() {
return $this->event->get_id();
}
public function get_name() {
return $this->event->get_name();
}
public function get_description() {
return $this->event->get_description();
}
public function get_location() {
return $this->event->get_location();
}
public function get_category() {
return $this->event->get_category();
}
public function get_course() {
return $this->event->get_course();
}
public function get_course_module() {
return $this->event->get_course_module();
}
public function get_group() {
return $this->event->get_group();
}
public function get_user() {
return $this->event->get_user();
}
public function get_type() {
return $this->event->get_type();
}
public function get_times() {
return $this->event->get_times();
}
public function get_repeats() {
return $this->event->get_repeats();
}
public function get_subscription() {
return $this->event->get_subscription();
}
public function is_visible() {
return $this->event->is_visible();
}
public function get_action() {
return $this->action;
}
/**
* Event component
* @return string
*/
public function get_component() {
return $this->event->get_component();
}
}
@@ -0,0 +1,42 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Calendar action event interface.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\entities;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for an action event class.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface action_event_interface extends event_interface {
/**
* Get the action event's action.
*
* @return action_interface
*/
public function get_action();
}
@@ -0,0 +1,63 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Action interface.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\entities;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for a action class.
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface action_interface {
/**
* Get the name of the action.
*
* @return string
*/
public function get_name();
/**
* Get the URL of the action.
*
* @return \moodle_url
*/
public function get_url();
/**
* Get the number of items that need actioning.
*
* @return int
*/
public function get_item_count();
/**
* Get the actions actionability.
*
* @return bool
*/
public function is_actionable();
}
@@ -0,0 +1,231 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Calendar event class.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\entities;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\proxies\proxy_interface;
use core_calendar\local\event\value_objects\description_interface;
use core_calendar\local\event\value_objects\times_interface;
/**
* Class representing a calendar event.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event implements event_interface {
/**
* @var int $id The event's id in the database.
*/
protected $id;
/**
* @var string $name The name of this event.
*/
protected $name;
/**
* @var description_interface $description Description for this event.
*/
protected $description;
/**
* @var string $location Location of this event.
*/
protected $location;
/**
* @var proxy_interface $category Category for this event.
*/
protected $category;
/**
* @var proxy_interface $course Course for this event.
*/
protected $course;
/**
* @var proxy_interface $group Group for this event.
*/
protected $group;
/**
* @var proxy_interface $user User for this event.
*/
protected $user;
/**
* @var event_collection_interface $repeats Collection of repeat events.
*/
protected $repeats;
/**
* @var proxy_interface $coursemodule The course module that created this event.
*/
protected $coursemodule;
/**
* @var string type The type of this event.
*/
protected $type;
/**
* @var times_interface $times The times for this event.
*/
protected $times;
/**
* @var bool $visible The visibility of this event.
*/
protected $visible;
/**
* @var string $component
*/
protected $component;
/**
* @var proxy_interface $subscription Subscription for this event.
*/
protected $subscription;
/**
* Constructor.
*
* @param int $id The event's ID in the database.
* @param string $name The event's name.
* @param description_interface $description The event's description.
* @param proxy_interface|null $category The category associated with the event.
* @param proxy_interface|null $course The course associated with the event.
* @param proxy_interface|null $group The group associated with the event.
* @param proxy_interface|null $user The user associated with the event.
* @param event_collection_interface|null $repeats Collection of repeat events.
* @param proxy_interface|null $coursemodule The course module that created the event.
* @param string $type The event's type.
* @param times_interface $times The times associated with the event.
* @param bool $visible The event's visibility. True for visible, false for invisible.
* @param proxy_interface|null $subscription The event's subscription.
* @param string $location The event's location.
* @param string $component The event's component.
*/
public function __construct(
$id,
$name,
description_interface $description,
?proxy_interface $category,
?proxy_interface $course,
?proxy_interface $group,
?proxy_interface $user,
?event_collection_interface $repeats,
?proxy_interface $coursemodule,
$type,
times_interface $times,
$visible,
proxy_interface $subscription = null,
$location = null,
$component = null
) {
$this->id = $id;
$this->name = $name;
$this->description = $description;
$this->location = $location;
$this->category = $category;
$this->course = $course;
$this->group = $group;
$this->user = $user;
$this->repeats = $repeats;
$this->coursemodule = $coursemodule;
$this->type = $type;
$this->times = $times;
$this->visible = $visible;
$this->subscription = $subscription;
$this->component = $component;
}
public function get_id() {
return $this->id;
}
public function get_name() {
return $this->name;
}
public function get_description() {
return $this->description;
}
public function get_location() {
return $this->location;
}
public function get_category() {
return $this->category;
}
public function get_course() {
return $this->course;
}
public function get_course_module() {
return $this->coursemodule;
}
public function get_group() {
return $this->group;
}
public function get_user() {
return $this->user;
}
public function get_type() {
return $this->type;
}
public function get_times() {
return $this->times;
}
public function get_repeats() {
return $this->repeats;
}
public function get_subscription() {
return $this->subscription;
}
public function is_visible() {
return $this->visible;
}
/**
* Resolved event component (frankenstyle name of activity module or the component)
* @return string|null
*/
public function get_component() {
return $this->get_course_module() ? 'mod_' . $this->get_course_module()->get('modname') : $this->component;
}
}
@@ -0,0 +1,49 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Interface for an event collection class.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\entities;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for an event collection class.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface event_collection_interface extends \IteratorAggregate {
/**
* Get the event collection's ID.
*
* @return int
*/
public function get_id();
/**
* Get the total number of repeats in the collection.
*
* @return int
*/
public function get_num();
}
@@ -0,0 +1,142 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Calendar event interface.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\entities;
use core_calendar\local\event\proxies\proxy_interface;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for an event class.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface event_interface {
/**
* Get the event's ID.
*
* @return integer
*/
public function get_id();
/**
* Get the event's name.
*
* @return string
*/
public function get_name();
/**
* Get the event's description.
*
* @return description_interface
*/
public function get_description();
/**
* Get the event's location.
*
* @return location_interface
*/
public function get_location();
/**
* Get the category object associated with the event.
*
* @return proxy_interface
*/
public function get_category();
/**
* Get the course object associated with the event.
*
* @return proxy_interface
*/
public function get_course();
/**
* Get the course module object that created the event.
*
* @return proxy_interface
*/
public function get_course_module();
/**
* Get the group object associated with the event.
*
* @return proxy_interface
*/
public function get_group();
/**
* Get the user object associated with the event.
*
* @return proxy_interface
*/
public function get_user();
/**
* Get the event's type.
*
* @return string
*/
public function get_type();
/**
* Get the times associated with the event.
*
* @return times_interface
*/
public function get_times();
/**
* Get repeats of this event or null if the event has no
* repeats.
*
* @return event_collection_interface|null
*/
public function get_repeats();
/**
* Get the event's subscription.
*
* @return proxy_interface
*/
public function get_subscription();
/**
* Get the event's visibility.
*
* @return bool true if the event is visible, false otherwise
*/
public function is_visible();
/**
* Resolved event component (frankenstyle name of activity module or the component)
* @return string|null
*/
public function get_component();
}
@@ -0,0 +1,151 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Event collection class.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\entities;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\factories\event_factory_interface;
/**
* Class representing a collection of repeat events.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class repeat_event_collection implements event_collection_interface {
/**
* @var int DB_QUERY_LIMIT How many records to pull from the DB at once.
*/
const DB_QUERY_LIMIT = 100;
/**
* @var int $parentid The ID of the event which the events in this collection are repeats of.
*/
protected $parentid;
/**
* @var \stdClass $parentrecord The parent event record from the database.
*/
protected $parentrecord;
/**
* @var event_factory_interface $factory Event factory.
*/
protected $factory;
/**
* @var int $num Total number of events that could be retrieved by this collection.
*/
protected $num;
/**
* Constructor.
*
* @param stdClass $dbrow The event dbrow that is being repeated.
* @param event_factory_interface $factory Event factory.
*/
public function __construct($dbrow, event_factory_interface $factory) {
$eventid = $dbrow->id;
$repeatid = $dbrow->repeatid;
if (empty($repeatid)) {
$this->parentrecord = $dbrow;
$this->parentid = $eventid;
} else {
$this->parentid = $repeatid;
}
if ($eventid === $repeatid) {
// This means the record we've been given is the parent
// record.
$this->parentrecord = $dbrow;
}
$this->factory = $factory;
}
public function get_id() {
return $this->parentid;
}
public function get_num() {
global $DB;
// Subtract one because the original event has repeatid = its own id.
return $this->num = max(
isset($this->num) ? $this->num : ($DB->count_records('event', ['repeatid' => $this->parentid]) - 1),
0
);
}
public function getIterator(): \Traversable {
$parentrecord = $this->get_parent_record();
foreach ($this->load_event_records() as $eventrecords) {
foreach ($eventrecords as $eventrecord) {
// In the case of the repeat event having unset information, fallback on the parent.
yield $this->factory->create_instance((object)array_merge((array)$parentrecord, (array)$eventrecord));
}
}
}
/**
* Return the parent DB record.
*
* @return \stdClass
*/
protected function get_parent_record() {
global $DB;
if (!isset($this->parentrecord)) {
$this->parentrecord = $DB->get_record('event', ['id' => $this->parentid]);
}
return $this->parentrecord;
}
/**
* Generate more event records.
*
* @param int $start Start offset.
* @return \stdClass[]
*/
protected function load_event_records($start = 0) {
global $DB;
while ($records = $DB->get_records_select(
'event',
'id <> :parentid AND repeatid = :repeatid',
[
'parentid' => $this->parentid,
'repeatid' => $this->parentid,
],
'id ASC',
'*',
$start,
self::DB_QUERY_LIMIT
)) {
yield $records;
$start += self::DB_QUERY_LIMIT;
}
}
}
@@ -0,0 +1,36 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Invalid callback exception.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\exceptions;
defined('MOODLE_INTERNAL') || die();
/**
* Invalid callback exception.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class invalid_callback_exception extends \moodle_exception {
}
@@ -0,0 +1,36 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* General invalid parameter exception.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\exceptions;
defined('MOODLE_INTERNAL') || die();
/**
* General invalid parameter exception.
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class invalid_parameter_exception extends \moodle_exception {
}
@@ -0,0 +1,36 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Invalid limit parameter exception.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\exceptions;
defined('MOODLE_INTERNAL') || die();
/**
* Invalid limit parameter exception.
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class limit_invalid_parameter_exception extends invalid_parameter_exception {
}
@@ -0,0 +1,36 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Member does not exist exception.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\exceptions;
defined('MOODLE_INTERNAL') || die();
/**
* Member does not exist exception.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class member_does_not_exist_exception extends \moodle_exception {
}
@@ -0,0 +1,36 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Invalid timesort parameter exception.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\exceptions;
defined('MOODLE_INTERNAL') || die();
/**
* Invalid timesort parameter exception.
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class timesort_invalid_parameter_exception extends invalid_parameter_exception {
}
@@ -0,0 +1,40 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Action factory interface.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\factories;
defined('MOODLE_INTERNAL') || die();
interface action_factory_interface {
/**
* Creates an instance of an action.
*
* @param string $name The action's name.
* @param \moodle_url $url The action's URL.
* @param int $itemcount The number of items needing action.
* @param bool $actionable The action's actionability.
* @return \core_calendar\local\event\entities\action_interface The action.
*/
public function create_instance($name, \moodle_url $url, $itemcount, $actionable);
}
@@ -0,0 +1,207 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Abstract event factory.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\factories;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\entities\event;
use core_calendar\local\event\entities\repeat_event_collection;
use core_calendar\local\event\exceptions\invalid_callback_exception;
use core_calendar\local\event\proxies\cm_info_proxy;
use core_calendar\local\event\proxies\coursecat_proxy;
use core_calendar\local\event\proxies\std_proxy;
use core_calendar\local\event\value_objects\event_description;
use core_calendar\local\event\value_objects\event_times;
use core_calendar\local\event\entities\event_interface;
/**
* Abstract factory for creating calendar events.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class event_abstract_factory implements event_factory_interface {
/**
* @var callable $actioncallbackapplier Function to apply component action callbacks.
*/
protected $actioncallbackapplier;
/**
* @var callable $visibilitycallbackapplier Function to apply component visibility callbacks.
*/
protected $visibilitycallbackapplier;
/**
* @var array Course cache for use with get_course_cached.
*/
protected $coursecachereference;
/**
* @var array Module cache reference for use with get_module_cached.
*/
protected $modulecachereference;
/**
* @var callable Bail out check for create_instance.
*/
protected $bailoutcheck;
/**
* Applies component actions to the event.
*
* @param event_interface $event The event to be updated.
* @return event_interface The potentially modified event.
*/
abstract protected function apply_component_action(event_interface $event);
/**
* Exposes the event (or not).
*
* @param event_interface $event The event to potentially expose.
* @return event_interface|null The exposed event or null.
*/
abstract protected function expose_event(event_interface $event);
/**
* Constructor.
*
* @param callable $actioncallbackapplier Function to apply component action callbacks.
* @param callable $visibilitycallbackapplier Function to apply component visibility callbacks.
* @param callable $bailoutcheck Function to test if we can return null early.
* @param array $coursecachereference Cache to use with get_course_cached.
* @param array $modulecachereference Cache to use with get_module_cached.
*/
public function __construct(
callable $actioncallbackapplier,
callable $visibilitycallbackapplier,
callable $bailoutcheck,
array &$coursecachereference,
array &$modulecachereference
) {
$this->actioncallbackapplier = $actioncallbackapplier;
$this->visibilitycallbackapplier = $visibilitycallbackapplier;
$this->bailoutcheck = $bailoutcheck;
$this->coursecachereference = &$coursecachereference;
$this->modulecachereference = &$modulecachereference;
}
public function create_instance(\stdClass $dbrow) {
if ($dbrow->modulename && $dbrow->instance && $dbrow->courseid == 0) {
// Some events (for example user overrides) may contain module instance but not course id. Find course id.
$cm = calendar_get_module_cached($this->modulecachereference, $dbrow->modulename, $dbrow->instance);
$dbrow->courseid = $cm->course;
}
$bailcheck = $this->bailoutcheck;
$bail = $bailcheck($dbrow);
if (!is_bool($bail)) {
throw new invalid_callback_exception(
'Bail check must return true or false'
);
}
if ($bail) {
return null;
}
$category = null;
$course = null;
$group = null;
$user = null;
$module = null;
$subscription = null;
$component = null;
if ($dbrow->modulename && $dbrow->instance) {
$module = new cm_info_proxy($dbrow->modulename, $dbrow->instance, $dbrow->courseid);
}
if ($dbrow->categoryid) {
$category = new coursecat_proxy($dbrow->categoryid);
}
$course = new std_proxy($dbrow->courseid, function($id) {
return calendar_get_course_cached($this->coursecachereference, $id);
});
if ($dbrow->groupid) {
$group = new std_proxy($dbrow->groupid, function($id) {
return calendar_get_group_cached($id);
});
}
if ($dbrow->userid) {
$user = new std_proxy($dbrow->userid, function($id) {
global $DB;
return $DB->get_record('user', ['id' => $id]);
});
}
if ($dbrow->subscriptionid) {
$subscription = new std_proxy($dbrow->subscriptionid, function($id) {
return calendar_get_subscription($id);
});
}
if (!empty($dbrow->repeatid)) {
$repeatcollection = new repeat_event_collection($dbrow, $this);
} else {
$repeatcollection = null;
}
if (!empty($dbrow->component)) {
$component = $dbrow->component;
}
$event = new event(
$dbrow->id,
$dbrow->name,
new event_description($dbrow->description, $dbrow->format),
$category,
$course,
$group,
$user,
$repeatcollection,
$module,
$dbrow->eventtype,
new event_times(
(new \DateTimeImmutable())->setTimestamp($dbrow->timestart),
(new \DateTimeImmutable())->setTimestamp($dbrow->timestart + $dbrow->timeduration),
(new \DateTimeImmutable())->setTimestamp($dbrow->timesort ? $dbrow->timesort : $dbrow->timestart),
(new \DateTimeImmutable())->setTimestamp($dbrow->timemodified),
(new \DateTimeImmutable())->setTimestamp($dbrow->timesort ? usergetmidnight($dbrow->timesort) : 0)
),
!empty($dbrow->visible),
$subscription,
$dbrow->location,
$component
);
$isactionevent = !empty($dbrow->type) && $dbrow->type == CALENDAR_EVENT_TYPE_ACTION;
return $isactionevent ? $this->expose_event($this->apply_component_action($event)) : $event;
}
}
@@ -0,0 +1,62 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Event factory class.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\factories;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\exceptions\invalid_callback_exception;
use core_calendar\local\event\entities\event_interface;
/**
* Event factory class.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event_factory extends event_abstract_factory {
protected function apply_component_action(event_interface $event) {
$callbackapplier = $this->actioncallbackapplier;
$callbackresult = $callbackapplier($event);
if (!$callbackresult instanceof event_interface) {
throw new invalid_callback_exception(
'Event factory action callback applier must return an instance of event_interface');
}
return $callbackresult;
}
protected function expose_event(event_interface $event) {
$callbackapplier = $this->visibilitycallbackapplier;
$callbackresult = $callbackapplier($event);
if (!is_bool($callbackresult)) {
throw new invalid_callback_exception('Event factory visibility callback applier must return true or false');
}
return $callbackresult === true ? $event : null;
}
}
@@ -0,0 +1,43 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Event factory interface.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\factories;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for an event factory class.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface event_factory_interface {
/**
* Creates an instance of an event.
*
* @param \stdClass $dbrow The event row from the database.
* @return \core_calendar\local\event\entities\event_interface
*/
public function create_instance(\stdClass $dbrow);
}
@@ -0,0 +1,248 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mform for creating a calendar event. Based on the old event form.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\forms;
use context_system;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/lib/formslib.php');
/**
* The mform class for creating a calendar event.
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class create extends \moodleform {
use eventtype;
/**
* Build the editor options using the given context.
*
* @param \context $context A Moodle context
* @return array
*/
public static function build_editor_options(\context $context) {
global $CFG;
return [
'context' => $context,
'maxfiles' => EDITOR_UNLIMITED_FILES,
'maxbytes' => $CFG->maxbytes,
'noclean' => true,
'autosave' => false
];
}
/**
* The form definition
*/
public function definition() {
global $PAGE;
$mform = $this->_form;
$starttime = isset($this->_customdata['starttime']) ? $this->_customdata['starttime'] : 0;
$editoroptions = !(empty($this->_customdata['editoroptions'])) ? $this->_customdata['editoroptions'] : null;
$courseid = !(empty($this->_customdata['courseid'])) ? $this->_customdata['courseid'] : null;
$eventtypes = $this->_customdata['eventtypes'];
if (in_array(true, $eventtypes, true) === false) {
throw new \moodle_exception('nopermissiontoupdatecalendar');
}
$mform->setDisableShortforms();
$mform->disable_form_change_checker();
// Empty string so that the element doesn't get rendered.
$mform->addElement('header', 'general', '');
$this->add_default_hidden_elements($mform);
// Event name field.
$mform->addElement('text', 'name', get_string('eventname', 'calendar'), 'size="50"');
$mform->addRule('name', get_string('required'), 'required', null, 'client');
$mform->setType('name', PARAM_TEXT);
// Event time start field.
$mform->addElement('date_time_selector', 'timestart', get_string('date'), ['defaulttime' => $starttime]);
// Add the select elements for the available event types.
$this->add_event_type_elements($mform, $eventtypes);
// Start of advanced elements.
// Advanced elements are not visible to the user by default.
// They are displayed through the user of a show more / less button.
$mform->addElement('editor', 'description', get_string('eventdescription', 'calendar'), ['rows' => 3], $editoroptions);
$mform->setType('description', PARAM_RAW);
$mform->setAdvanced('description');
$mform->addElement('text', 'location', get_string('location', 'moodle'), 'size="50"');
$mform->setType('location', PARAM_RAW_TRIMMED);
$mform->setAdvanced('location');
// Add the variety of elements allowed for selecting event duration.
$this->add_event_duration_elements($mform);
// Add the form elements for repeating events.
$this->add_event_repeat_elements($mform);
// Add the javascript required to enhance this mform.
$PAGE->requires->js_call_amd('core_calendar/event_form', 'init', [$mform->getAttribute('id')]);
}
/**
* A bit of custom validation for this form
*
* @param array $data An assoc array of field=>value
* @param array $files An array of files
* @return array
*/
public function validation($data, $files) {
global $DB;
$errors = parent::validation($data, $files);
$eventtype = isset($data['eventtype']) ? $data['eventtype'] : null;
$coursekey = ($eventtype == 'group') ? 'groupcourseid' : 'courseid';
$courseid = (!empty($data[$coursekey])) ? $data[$coursekey] : null;
$categoryid = (!empty($data['categoryid'])) ? $data['categoryid'] : null;
$eventtypes = $this->_customdata['eventtypes'];
if (empty($eventtype) || !isset($eventtypes[$eventtype]) || $eventtypes[$eventtype] == false) {
$errors['eventtype'] = get_string('invalideventtype', 'calendar');
}
if ($courseid && $courseid > 0) {
if ($course = $DB->get_record('course', ['id' => $courseid])) {
if ($data['timestart'] < $course->startdate) {
$errors['timestart'] = get_string('errorbeforecoursestart', 'calendar');
}
} else {
$errors[$coursekey] = get_string('invalidcourse', 'error');
}
}
if ($eventtype == 'course' && empty($courseid)) {
$errors['courseid'] = get_string('selectacourse');
}
if ($eventtype == 'category' && empty($categoryid)) {
$errors['categoryid'] = get_string('selectacategory');
}
if ($eventtype == 'group' && (!empty($courseid) && empty($data['groupid']))) {
$errors['groupcourseid'] = get_string('nogroups', 'core_group');
}
if ($eventtype == 'group' && empty($courseid)) {
$errors['groupcourseid'] = get_string('selectacourse');
}
if ($data['duration'] == 1 && $data['timestart'] > $data['timedurationuntil']) {
$errors['durationgroup'] = get_string('invalidtimedurationuntil', 'calendar');
} else if ($data['duration'] == 2 && (trim($data['timedurationminutes']) == '' || $data['timedurationminutes'] < 1)) {
$errors['durationgroup'] = get_string('invalidtimedurationminutes', 'calendar');
}
return $errors;
}
/**
* Add the list of hidden elements that should appear in this form each
* time. These elements will never be visible to the user.
*
* @param MoodleQuickForm $mform
*/
protected function add_default_hidden_elements($mform) {
global $USER;
// Add some hidden fields.
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$mform->setDefault('id', 0);
$mform->addElement('hidden', 'userid');
$mform->setType('userid', PARAM_INT);
$mform->setDefault('userid', $USER->id);
$mform->addElement('hidden', 'modulename');
$mform->setType('modulename', PARAM_INT);
$mform->setDefault('modulename', '');
$mform->addElement('hidden', 'instance');
$mform->setType('instance', PARAM_INT);
$mform->setDefault('instance', 0);
$mform->addElement('hidden', 'visible');
$mform->setType('visible', PARAM_INT);
$mform->setDefault('visible', 1);
}
/**
* Add the various elements to express the duration options available
* for an event.
*
* @param MoodleQuickForm $mform
*/
protected function add_event_duration_elements($mform) {
$group = [];
$group[] = $mform->createElement('radio', 'duration', null, get_string('durationnone', 'calendar'), 0);
$group[] = $mform->createElement('radio', 'duration', null, get_string('durationuntil', 'calendar'), 1);
$group[] = $mform->createElement('date_time_selector', 'timedurationuntil', '');
$group[] = $mform->createElement('radio', 'duration', null, get_string('durationminutes', 'calendar'), 2);
$group[] = $mform->createElement('text', 'timedurationminutes', get_string('durationminutes', 'calendar'));
$mform->addGroup($group, 'durationgroup', get_string('eventduration', 'calendar'), '<br />', false);
$mform->setAdvanced('durationgroup');
$mform->disabledIf('timedurationuntil', 'duration', 'noteq', 1);
$mform->disabledIf('timedurationuntil[day]', 'duration', 'noteq', 1);
$mform->disabledIf('timedurationuntil[month]', 'duration', 'noteq', 1);
$mform->disabledIf('timedurationuntil[year]', 'duration', 'noteq', 1);
$mform->disabledIf('timedurationuntil[hour]', 'duration', 'noteq', 1);
$mform->disabledIf('timedurationuntil[minute]', 'duration', 'noteq', 1);
$mform->setType('timedurationminutes', PARAM_INT);
$mform->disabledIf('timedurationminutes', 'duration', 'noteq', 2);
$mform->setDefault('duration', 0);
}
/**
* Add the repeat elements for the form when creating a new event.
*
* @param MoodleQuickForm $mform
*/
protected function add_event_repeat_elements($mform) {
$mform->addElement('checkbox', 'repeat', get_string('repeatevent', 'calendar'), null);
$mform->addElement('text', 'repeats', get_string('repeatweeksl', 'calendar'), 'maxlength="10" size="10"');
$mform->setType('repeats', PARAM_INT);
$mform->setDefault('repeats', 1);
$mform->disabledIf('repeats', 'repeat', 'notchecked');
$mform->setAdvanced('repeat');
$mform->setAdvanced('repeats');
}
}
@@ -0,0 +1,122 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The trait for adding eventtype fields to a form.
*
* @package core_calendar
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\forms;
defined('MOODLE_INTERNAL') || die();
/**
* The trait for adding eventtype fields to a form.
*
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait eventtype {
/**
* Add the appropriate elements for the available event types.
*
* If the only event type available is 'user' then we add a hidden
* element because there is nothing for the user to choose.
*
* If more than one type is available then we add the elements as
* follows:
* - Always add the event type selector
* - Elements per type:
* - course: add an additional select element with each
* course as an option.
* - group: add a select element for the course (different
* from the above course select) and a select
* element for the group.
*
* @param MoodleQuickForm $mform
* @param array $eventtypes The available event types for the user
*/
protected function add_event_type_elements($mform, $eventtypes) {
global $CFG, $DB;
$options = [];
if (!empty($eventtypes['user'])) {
$options['user'] = get_string('user', 'calendar');
}
if (!empty($eventtypes['group'])) {
$options['group'] = get_string('group', 'calendar');
}
if (!empty($eventtypes['course'])) {
$options['course'] = get_string('course', 'calendar');
}
if (!empty($eventtypes['category'])) {
$options['category'] = get_string('category', 'calendar');
}
if (!empty($eventtypes['site'])) {
$options['site'] = get_string('site', 'calendar');
}
// If we only have one event type and it's 'user' event then don't bother
// rendering the select boxes because there is no choice for the user to
// make.
if (!empty($eventtypes['user']) && count($options) == 1) {
$mform->addElement('hidden', 'eventtype');
$mform->setType('eventtype', PARAM_TEXT);
$mform->hardFreeze('eventtype');
$mform->setConstant('eventtype', 'user');
return;
} else {
$mform->addElement('select', 'eventtype', get_string('eventkind', 'calendar'), $options);
}
if (!empty($eventtypes['category'])) {
$categoryoptions = [];
foreach (\core_course_category::make_categories_list('moodle/category:manage') as $id => $category) {
$categoryoptions[$id] = $category;
}
$mform->addElement('autocomplete', 'categoryid', get_string('category'), $categoryoptions);
$mform->hideIf('categoryid', 'eventtype', 'noteq', 'category');
}
$showall = is_siteadmin() && !empty($CFG->calendar_adminseesall);
if (!empty($eventtypes['course'])) {
$mform->addElement('course', 'courseid', get_string('course'), ['limittoenrolled' => !$showall]);
$mform->hideIf('courseid', 'eventtype', 'noteq', 'course');
}
if (!empty($eventtypes['group'])) {
$groups = !(empty($this->_customdata['groups'])) ? $this->_customdata['groups'] : null;
// Get the list of courses without groups to filter on the course selector.
$sql = "SELECT c.id
FROM {course} c
WHERE c.id NOT IN (
SELECT DISTINCT courseid FROM {groups}
)";
$coursesnogroup = $DB->get_records_sql($sql);
$mform->addElement('course', 'groupcourseid', get_string('course'), ['limittoenrolled' => !$showall,
'exclude' => array_keys($coursesnogroup)]);
$mform->hideIf('groupcourseid', 'eventtype', 'noteq', 'group');
$mform->addElement('select', 'groupid', get_string('group'), $groups);
$mform->hideIf('groupid', 'eventtype', 'noteq', 'group');
// We handle the group select hide/show actions on the event_form module.
}
}
}
@@ -0,0 +1,173 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Allows the user to manage calendar subscriptions.
*
* @copyright 2012 Jonathan Harker
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package calendar
*/
namespace core_calendar\local\event\forms;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
/**
* Form for adding a subscription to a Moodle course calendar.
* @copyright 2012 Jonathan Harker
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class managesubscriptions extends \moodleform {
use eventtype;
/**
* Defines the form used to add calendar subscriptions.
*/
public function definition() {
global $PAGE;
$mform = $this->_form;
$eventtypes = calendar_get_allowed_event_types();
if (in_array(true, $eventtypes, true) === false) {
throw new \moodle_exception('nopermissiontoupdatecalendar');
}
// Name.
$mform->addElement('text', 'name', get_string('subscriptionname', 'calendar'), array('maxsize' => '255', 'size' => '40'));
$mform->addRule('name', get_string('required'), 'required', null, 'client');
$mform->setType('name', PARAM_TEXT);
// Import from (url | importfile).
$choices = array(CALENDAR_IMPORT_FROM_FILE => get_string('importfromfile', 'calendar'),
CALENDAR_IMPORT_FROM_URL => get_string('importfromurl', 'calendar'));
$mform->addElement('select', 'importfrom', get_string('importcalendarfrom', 'calendar'), $choices);
$mform->setDefault('importfrom', CALENDAR_IMPORT_FROM_URL);
// URL.
$mform->addElement('text', 'url', get_string('importfromurl', 'calendar'), array('maxsize' => '255', 'size' => '50'));
// Cannot set as PARAM_URL since we need to allow webcal:// protocol.
$mform->setType('url', PARAM_RAW);
$mform->setForceLtr('url');
// Poll interval
$choices = calendar_get_pollinterval_choices();
$mform->addElement('select', 'pollinterval', get_string('pollinterval', 'calendar'), $choices);
$mform->setDefault('pollinterval', 604800);
$mform->addHelpButton('pollinterval', 'pollinterval', 'calendar');
$mform->setType('pollinterval', PARAM_INT);
// Import file
$mform->addElement('filepicker', 'importfile', get_string('importfromfile', 'calendar'), null, array('accepted_types' => '.ics'));
// Disable appropriate elements depending on import from value.
$mform->hideIf('pollinterval', 'importfrom', 'eq', CALENDAR_IMPORT_FROM_FILE);
$mform->hideIf('url', 'importfrom', 'eq', CALENDAR_IMPORT_FROM_FILE);
$mform->hideIf('importfile', 'importfrom', 'eq', CALENDAR_IMPORT_FROM_URL);
// Add the select elements for the available event types.
$this->add_event_type_elements($mform, $eventtypes);
// Eventtype: 0 = user, 1 = site, anything else = course ID.
$mform->addElement('submit', 'add', get_string('importcalendar', 'calendar'));
// Add the javascript required to enhance this mform.
$PAGE->requires->js_call_amd('core_calendar/event_form', 'init', [$mform->getAttribute('id')]);
}
/**
* Validates the returned data.
*
* @param array $data
* @param array $files
* @return array
*/
public function validation($data, $files) {
global $USER;
$errors = parent::validation($data, $files);
$eventtype = isset($data['eventtype']) ? $data['eventtype'] : null;
$coursekey = ($eventtype == 'group') ? 'groupcourseid' : 'courseid';
$courseid = (!empty($data[$coursekey])) ? $data[$coursekey] : null;
$eventtypes = calendar_get_allowed_event_types($courseid);
if (empty($eventtype) || !isset($eventtypes[$eventtype]) || $eventtypes[$eventtype] == false) {
$errors['eventtype'] = get_string('invalideventtype', 'calendar');
}
if ($data['importfrom'] == CALENDAR_IMPORT_FROM_FILE) {
if (empty($data['importfile'])) {
$errors['importfile'] = get_string('errorrequiredurlorfile', 'calendar');
} else {
// Make sure the file area is not empty and contains only one file.
$draftitemid = $data['importfile'];
$fs = get_file_storage();
$usercontext = \context_user::instance($USER->id);
$files = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id DESC', false);
if (count($files) !== 1) {
$errors['importfile'] = get_string('errorrequiredurlorfile', 'calendar');
}
}
} else if (($data['importfrom'] == CALENDAR_IMPORT_FROM_URL)) {
if (empty($data['url'])) {
$errors['url'] = get_string('errorrequiredurlorfile', 'calendar');
} else {
// Clean input calendar url.
$url = clean_param($data['url'], PARAM_URL);
try {
calendar_get_icalendar($url);
} catch (\moodle_exception $e) {
$errors['url'] = get_string('errorinvalidicalurl', 'calendar');
}
}
} else {
// Shouldn't happen.
$errors['url'] = get_string('errorrequiredurlorfile', 'calendar');
}
// Validate course/category event types (ensure appropriate field is also filled in).
if ($eventtype === 'course' && empty($data['courseid'])) {
$errors['courseid'] = get_string('selectacourse');
} else if ($eventtype === 'category' && empty($data['categoryid'])) {
$errors['categoryid'] = get_string('required');
}
return $errors;
}
public function definition_after_data() {
$mform =& $this->_form;
$mform->applyFilter('url', static::class . '::strip_webcal');
$mform->applyFilter('url', 'trim');
}
/**
* Replace webcal:// urls with http:// as
* curl does not understand this protocol
*
* @param string @url url to examine
* @return string url with webcal:// replaced
*/
public static function strip_webcal($url) {
if (strpos($url, 'webcal://') === 0) {
$url = str_replace('webcal://', 'http://', $url);
}
return $url;
}
}
@@ -0,0 +1,59 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mform for updating a calendar event. Based on the old event form.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\forms;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/lib/formslib.php');
/**
* The mform class for updating a calendar event.
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class update extends create {
/**
* Add the repeat elements for the form when editing an existing event.
*
* @param MoodleQuickForm $mform
*/
protected function add_event_repeat_elements($mform) {
$event = $this->_customdata['event'];
$mform->addElement('hidden', 'repeatid');
$mform->setType('repeatid', PARAM_INT);
if (!empty($event->repeatid)) {
$group = [];
$group[] = $mform->createElement('radio', 'repeateditall', null, get_string('repeateditall', 'calendar',
$event->eventrepeats), 1);
$group[] = $mform->createElement('radio', 'repeateditall', null, get_string('repeateditthis', 'calendar'), 0);
$mform->addGroup($group, 'repeatgroup', get_string('repeatedevents', 'calendar'), '<br />', false);
$mform->setDefault('repeateditall', 1);
$mform->setAdvanced('repeatgroup');
}
}
}
@@ -0,0 +1,139 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Event create form and update form mapper.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\mappers;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/calendar/lib.php');
/**
* Event create form and update form mapper class.
*
* This class will perform the necessary data transformations to take
* a legacy event and build the appropriate data structure for both the
* create and update event forms.
*
* It will also do the reverse transformation
* and take the returned form data and provide a data structure that can
* be used to set legacy event properties.
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class create_update_form_mapper implements create_update_form_mapper_interface {
/**
* Generate the appropriate data for the form from a legacy event.
*
* @param \calendar_event $legacyevent
* @return stdClass
*/
public function from_legacy_event_to_data(\calendar_event $legacyevent) {
$legacyevent->count_repeats();
$data = $legacyevent->properties();
$data->timedurationuntil = $legacyevent->timestart + $legacyevent->timeduration;
$data->duration = (empty($legacyevent->timeduration)) ? 0 : 1;
if ($legacyevent->eventtype == 'group') {
// Set up the correct value for the to display on the form.
$data->groupid = $legacyevent->groupid;
$data->groupcourseid = $legacyevent->courseid;
}
if ($legacyevent->eventtype == 'course') {
// Set up the correct value for the to display on the form.
$data->courseid = $legacyevent->courseid;
}
$data->description = [
'text' => $data->description,
'format' => $data->format
];
// Don't return context or subscription because they're not form values and break validation.
if (isset($data->context)) {
unset($data->context);
}
if (isset($data->subscription)) {
unset($data->subscription);
}
return $data;
}
/**
* Generate the appropriate calendar_event properties from the form data.
*
* @param \stdClass $data
* @return stdClass
*/
public function from_data_to_event_properties(\stdClass $data) {
$properties = clone($data);
if ($data->eventtype == 'group') {
if (isset($data->groupcourseid)) {
$properties->courseid = $data->groupcourseid;
unset($properties->groupcourseid);
}
if (isset($data->groupid)) {
$properties->groupid = $data->groupid;
}
} else {
// Default course id if none is set.
if (empty($properties->courseid)) {
if ($properties->eventtype == 'site') {
$properties->courseid = SITEID;
} else {
$properties->courseid = 0;
}
} else {
$properties->courseid = $data->courseid;
}
if (empty($properties->groupid)) {
$properties->groupid = 0;
}
}
// Decode the form fields back into valid event property.
$properties->timeduration = $this->get_time_duration_from_form_data($data);
return $properties;
}
/**
* A helper function to calculate the time duration for an event based on
* the event_form data.
*
* @param \stdClass $data event_form data
* @return int
*/
private function get_time_duration_from_form_data(\stdClass $data) {
if ($data->duration == 1) {
return $data->timedurationuntil - $data->timestart;
} else if ($data->duration == 2) {
return $data->timedurationminutes * MINSECS;
} else {
return 0;
}
}
}
@@ -0,0 +1,53 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Create update form mapper interface.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\mappers;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/calendar/lib.php');
/**
* Interface for a create_update_form_mapper class
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface create_update_form_mapper_interface {
/**
* Generate the appropriate data for the form from a legacy event.
*
* @param \calendar_event $legacyevent
* @return stdClass
*/
public function from_legacy_event_to_data(\calendar_event $legacyevent);
/**
* Generate the appropriate calendar_event properties from the form data.
*
* @param \stdClass $data
* @return stdClass
*/
public function from_data_to_event_properties(\stdClass $data);
}
@@ -0,0 +1,150 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Event mapper.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\mappers;
defined('MOODLE_INTERNAL') || die();
use core_calendar\event;
use core_calendar\local\event\entities\action_event_interface;
use core_calendar\local\event\entities\event_interface;
use core_calendar\local\event\factories\event_factory_interface;
/**
* Event mapper class.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event_mapper implements event_mapper_interface {
/**
* @var event_factory_interface $factory Event factory.
*/
protected $factory;
/**
* Constructor.
*
* @param event_factory_interface $factory Event factory.
*/
public function __construct(event_factory_interface $factory) {
$this->factory = $factory;
}
public function from_legacy_event_to_event(\calendar_event $legacyevent) {
$coalesce = function($property) use ($legacyevent) {
try {
return $legacyevent->$property;
} catch (\coding_exception $e) {
// The magic setter throews an exception if the
// property doesn't exist.
return null;
}
};
return $this->factory->create_instance(
(object)[
'id' => $coalesce('id'),
'name' => $coalesce('name'),
'description' => $coalesce('description'),
'location' => $coalesce('location'),
'format' => $coalesce('format'),
'categoryid' => $coalesce('categoryid'),
'courseid' => $coalesce('courseid'),
'groupid' => $coalesce('groupid'),
'userid' => $coalesce('userid'),
'repeatid' => $coalesce('repeatid'),
'component' => $coalesce('component'),
'modulename' => $coalesce('modulename'),
'instance' => $coalesce('instance'),
'eventtype' => $coalesce('eventtype'),
'timestart' => $coalesce('timestart'),
'timeduration' => $coalesce('timeduration'),
'timemodified' => $coalesce('timemodified'),
'timesort' => $coalesce('timesort'),
'visible' => $coalesce('visible'),
'subscriptionid' => $coalesce('subscriptionid')
]
);
}
public function from_event_to_legacy_event(event_interface $event) {
$action = ($event instanceof action_event_interface) ? $event->get_action() : null;
$timeduration = $event->get_times()->get_end_time()->getTimestamp() - $event->get_times()->get_start_time()->getTimestamp();
$properties = $this->from_event_to_stdclass($event);
// Normalise for the legacy event because it wants zero rather than null.
$properties->courseid = empty($properties->courseid) ? 0 : $properties->courseid;
$properties->categoryid = empty($properties->categoryid) ? 0 : $properties->categoryid;
$properties->groupid = empty($properties->groupid) ? 0 : $properties->groupid;
$properties->userid = empty($properties->userid) ? 0 : $properties->userid;
$properties->component = empty($properties->component) ? 0 : $properties->component;
$properties->modulename = empty($properties->modulename) ? 0 : $properties->modulename;
$properties->instance = empty($properties->instance) ? 0 : $properties->instance;
$properties->repeatid = empty($properties->repeatid) ? 0 : $properties->repeatid;
return new \calendar_event($properties);
}
public function from_event_to_stdclass(event_interface $event) {
$action = ($event instanceof action_event_interface) ? $event->get_action() : null;
$timeduration = $event->get_times()->get_end_time()->getTimestamp() - $event->get_times()->get_start_time()->getTimestamp();
return (object)$this->from_event_to_assoc_array($event);
}
public function from_event_to_assoc_array(event_interface $event) {
$action = ($event instanceof action_event_interface) ? $event->get_action() : null;
$timeduration = $event->get_times()->get_end_time()->getTimestamp() - $event->get_times()->get_start_time()->getTimestamp();
return [
'id' => $event->get_id(),
'name' => $event->get_name(),
'description' => $event->get_description()->get_value(),
'format' => $event->get_description()->get_format(),
'location' => $event->get_location(),
'courseid' => $event->get_course() ? $event->get_course()->get('id') : null,
'categoryid' => $event->get_category() ? $event->get_category()->get('id') : null,
'groupid' => $event->get_group() ? $event->get_group()->get('id') : null,
'userid' => $event->get_user() ? $event->get_user()->get('id') : null,
'repeatid' => $event->get_repeats() ? $event->get_repeats()->get_id() : null,
'component' => $event->get_component(),
'modulename' => $event->get_course_module() ? $event->get_course_module()->get('modname') : null,
'instance' => $event->get_course_module() ? $event->get_course_module()->get('instance') : null,
'eventtype' => $event->get_type(),
'timestart' => $event->get_times()->get_start_time()->getTimestamp(),
'timeduration' => $timeduration,
'timesort' => $event->get_times()->get_sort_time()->getTimestamp(),
'timeusermidnight' => $event->get_times()->get_usermidnight_time()->getTimestamp(),
'visible' => $event->is_visible() ? 1 : 0,
'timemodified' => $event->get_times()->get_modified_time()->getTimestamp(),
'subscriptionid' => $event->get_subscription() ? $event->get_subscription()->get('id') : null,
'actionname' => $action ? $action->get_name() : null,
'actionurl' => $action ? $action->get_url() : null,
'actionnum' => $action ? $action->get_item_count() : null,
'actionactionable' => $action ? $action->is_actionable() : null,
'sequence' => 1
];
}
}
@@ -0,0 +1,70 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Event mapper interface.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\mappers;
defined('MOODLE_INTERNAL') || die();
use core_calendar\event;
use core_calendar\local\event\entities\event_interface;
/**
* Interface for an event mapper class
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface event_mapper_interface {
/**
* Map a legacy event to an event.
*
* @param \calendar_event $event The legacy event.
* @return event_interface The mapped event.
*/
public function from_legacy_event_to_event(\calendar_event $event);
/**
* Map an event to a legacy event.
*
* @param event_interface $event The legacy event.
* @return \calendar_event The mapped legacy event.
*/
public function from_event_to_legacy_event(event_interface $event);
/**
* Map an event to a stdClass
*
* @param event_interface $event The legacy event.
* @return \stdClass The mapped stdClass.
*/
public function from_event_to_stdclass(event_interface $event);
/**
* Map an event to an associative array.
*
* @param event_interface $event The legacy event.
* @return array The mapped legacy event array.
*/
public function from_event_to_assoc_array(event_interface $event);
}
@@ -0,0 +1,83 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course module cm_info proxy.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\proxies;
defined('MOODLE_INTERNAL') || die();
/**
* Course module stdClass proxy.
*
* This implementation differs from the regular std_proxy in that it takes
* a module name and instance instead of an id to construct the proxied class.
*
* This is needed as the event table does not store the id of course modules
* instead it stores the module name and instance.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cm_info_proxy implements proxy_interface {
/** @var \stdClass */
protected $base;
/** @var \cm_info */
protected $cm;
/**
* cm_info_proxy constructor.
*
* @param string $modname The module name.
* @param int $instance The module instance.
* @param int $courseid course id this module belongs to
*/
public function __construct($modname, $instance, $courseid) {
$this->base = (object)['course' => $courseid, 'modname' => $modname, 'instance' => $instance];
}
/**
* Retrieve a member of the proxied class.
*
* @param string $member The name of the member to retrieve
* @return mixed The member.
*/
public function get($member) {
if ($this->base && property_exists($this->base, $member)) {
return $this->base->{$member};
}
return $this->get_proxied_instance()->{$member};
}
/**
* Get the full instance of the proxied class.
*
* @return \cm_info
*/
public function get_proxied_instance() {
if (!$this->cm) {
$this->cm = get_fast_modinfo($this->base->course)->instances[$this->base->modname][$this->base->instance];
}
return $this->cm;
}
}
@@ -0,0 +1,90 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course category proxy.
*
* @package core_calendar
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\proxies;
defined('MOODLE_INTERNAL') || die();
/**
* Course category proxy.
*
* This returns an instance of a coursecat rather than a stdClass.
*
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class coursecat_proxy implements proxy_interface {
/**
* @var int $id The ID of the database record.
*/
protected $id;
/**
* @var \stdClass $base Base class to get members from.
*/
protected $base;
/**
* @var \core_course_category $category The proxied instance.
*/
protected $category;
/**
* coursecat_proxy constructor.
*
* @param int $id The ID of the record in the database.
*/
public function __construct($id) {
$this->id = $id;
$this->base = (object) [
'id' => $id,
];
}
/**
* Retrieve a member of the proxied class.
*
* @param string $member The name of the member to retrieve
* @return mixed The member.
*/
public function get($member) {
if ($this->base && property_exists($this->base, $member)) {
return $this->base->{$member};
}
return $this->get_proxied_instance()->{$member};
}
/**
* Get the full instance of the proxied class.
*
* @return \core_course_category
*/
public function get_proxied_instance(): \core_course_category {
if (!$this->category) {
$this->category = \core_course_category::get($this->id, IGNORE_MISSING, true);
}
return $this->category;
}
}
@@ -0,0 +1,52 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Proxy interface.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\proxies;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for a proxy class.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface proxy_interface {
/**
* Retrieve a member of the proxied class.
*
* @param string $member The name of the member to retrieve
* @throws \core_calendar\local\event\exceptions\member_does_not_exist_exception If the proxied class does not have the
* requested member.
* @return mixed The member.
*/
public function get($member);
/**
* Get the full instance of the proxied class.
*
* @return mixed
*/
public function get_proxied_instance();
}
@@ -0,0 +1,101 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* std_proxy class.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\proxies;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\exceptions\member_does_not_exist_exception;
/**
* stdClass proxy.
*
* This class is intended to proxy things like user, group, etc 'classes'
* It will only run the callback to load the object from the DB when necessary.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class std_proxy implements proxy_interface {
/**
* @var int $id The ID of the database record.
*/
protected $id;
/**
* @var \stdClass $class The class we are proxying.
*/
protected $class;
/**
* @var callable $callback Callback to run which will load the class to proxy.
*/
protected $callback;
/**
* @var array $callbackargs Array of arguments to pass to the callback.
*/
protected $callbackargs;
/**
* @var \stdClass $base Base class to get members from.
*/
protected $base;
/**
* Constructor.
*
* @param int $id The ID of the record in the database.
* @param callable $callback Callback to load the class.
* @param \stdClass $base Class containing base values.
*/
public function __construct($id, callable $callback, \stdClass $base = null) {
$this->id = $id;
$this->callbackargs = [$id];
$this->callback = $callback;
$this->base = $base;
}
public function get($member) {
if ($member === 'id') {
return $this->id;
}
if ($this->base && property_exists($this->base, $member)) {
return $this->base->{$member};
}
if (!property_exists($this->get_proxied_instance(), $member)) {
throw new member_does_not_exist_exception(sprintf('Member %s does not exist', $member));
}
return $this->get_proxied_instance()->{$member};
}
public function get_proxied_instance() {
$callback = $this->callback;
return $this->class = $this->class ? $this->class : $callback(...$this->callbackargs);
}
}
@@ -0,0 +1,385 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Raw event retrieval strategy.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\strategies;
defined('MOODLE_INTERNAL') || die();
/**
* Raw event retrieval strategy.
*
* This strategy is based on what used to be the calendar API's get_events function.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_interface {
public function get_raw_events(
array $usersfilter = null,
array $groupsfilter = null,
array $coursesfilter = null,
array $categoriesfilter = null,
array $whereconditions = null,
array $whereparams = null,
$ordersql = null,
$offset = null,
$limitnum = null,
$ignorehidden = true
) {
return $this->get_raw_events_legacy_implementation(
!is_null($usersfilter) ? $usersfilter : true, // True means no filter in old implementation.
!is_null($groupsfilter) ? $groupsfilter : true,
!is_null($coursesfilter) ? $coursesfilter : true,
!is_null($categoriesfilter) ? $categoriesfilter : true,
$whereconditions,
$whereparams,
$ordersql,
$offset,
$limitnum,
$ignorehidden
);
}
/**
* The legacy implementation with minor tweaks.
*
* @param array|int|boolean $users array of users, user id or boolean for all/no user events
* @param array|int|boolean $groups array of groups, group id or boolean for all/no group events
* @param array|int|boolean $courses array of courses, course id or boolean for all/no course events
* @param array $whereconditions The conditions in the WHERE clause.
* @param array $whereparams The parameters for the WHERE clause.
* @param string $ordersql The ORDER BY clause.
* @param int $offset Offset.
* @param int $limitnum Limit.
* @param boolean $ignorehidden whether to select only visible events or all events
* @return array $events of selected events or an empty array if there aren't any (or there was an error)
*/
protected function get_raw_events_legacy_implementation(
$users,
$groups,
$courses,
$categories,
$whereconditions,
$whereparams,
$ordersql,
$offset,
$limitnum,
$ignorehidden
) {
global $DB;
$params = array();
// Quick test.
if (empty($users) && empty($groups) && empty($courses) && empty($categories)) {
return array();
}
if (is_numeric($users)) {
$users = array($users);
}
if (is_numeric($groups)) {
$groups = array($groups);
}
if (is_numeric($courses)) {
$courses = array($courses);
}
if (is_numeric($categories)) {
$categories = array($categories);
}
// Array of filter conditions. To be concatenated by the OR operator.
$filters = [];
// User filter.
if (is_array($users) && !empty($users)) {
// Events from a number of users.
list($insqlusers, $inparamsusers) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED);
$filters[] = "(e.userid $insqlusers AND e.courseid = 0 AND e.groupid = 0 AND e.categoryid = 0)";
$params = array_merge($params, $inparamsusers);
} else if ($users === true) {
// Events from ALL users.
$filters[] = "(e.userid != 0 AND e.courseid = 0 AND e.groupid = 0 AND e.categoryid = 0)";
}
// Boolean false (no users at all): We don't need to do anything.
// Group filter.
if (is_array($groups) && !empty($groups)) {
// Events from a number of groups.
list($insqlgroups, $inparamsgroups) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED);
$filters[] = "e.groupid $insqlgroups";
$params = array_merge($params, $inparamsgroups);
} else if ($groups === true) {
// Events from ALL groups.
$filters[] = "e.groupid != 0";
}
// Boolean false (no groups at all): We don't need to do anything.
// Course filter.
if (is_array($courses) && !empty($courses)) {
list($insqlcourses, $inparamscourses) = $DB->get_in_or_equal($courses, SQL_PARAMS_NAMED);
$filters[] = "(e.groupid = 0 AND e.courseid $insqlcourses)";
$params = array_merge($params, $inparamscourses);
} else if ($courses === true) {
// Events from ALL courses.
$filters[] = "(e.groupid = 0 AND e.courseid != 0)";
}
// Category filter.
if (is_array($categories) && !empty($categories)) {
list($insqlcategories, $inparamscategories) = $DB->get_in_or_equal($categories, SQL_PARAMS_NAMED);
$filters[] = "(e.groupid = 0 AND e.courseid = 0 AND e.categoryid $insqlcategories)";
$params = array_merge($params, $inparamscategories);
} else if ($categories === true) {
// Events from ALL categories.
$filters[] = "(e.groupid = 0 AND e.courseid = 0 AND e.categoryid != 0)";
}
// Security check: if, by now, we have NOTHING in $whereclause, then it means
// that NO event-selecting clauses were defined. Thus, we won't be returning ANY
// events no matter what. Allowing the code to proceed might return a completely
// valid query with only time constraints, thus selecting ALL events in that time frame!
if (empty($filters)) {
return array();
}
// Build our clause for the filters.
$filterclause = implode(' OR ', $filters);
// Array of where conditions for our query. To be concatenated by the AND operator.
$whereconditions[] = "($filterclause)";
// Show visible only.
if ($ignorehidden) {
$whereconditions[] = "(e.visible = 1)";
}
// Build the main query's WHERE clause.
$whereclause = implode(' AND ', $whereconditions);
// Build SQL subquery and conditions for filtered events based on priorities.
$subquerytimeconditions = array_filter($whereconditions, function($condition) {
return (strpos($condition, 'time') !== false);
});
$subquerywhere = '';
$subqueryconditions = [];
$subqueryparams = [];
$allusercourses = [];
if (is_array($users) && !empty($users)) {
$userrecords = $DB->get_records_sql("SELECT * FROM {user} WHERE id $insqlusers", $inparamsusers);
foreach ($userrecords as $userrecord) {
// Get the user's courses. Otherwise, get the default courses being shown by the calendar.
$usercourses = calendar_get_default_courses(null, 'id, category, groupmode, groupmodeforce',
false, $userrecord->id);
// Set calendar filters.
list($usercourses, $usergroups, $user) = calendar_set_filters($usercourses, true, $userrecord);
$filteredcourses = is_array($courses) ? $courses : [$courses];
$filteredcourses = array_filter($usercourses, function($course) use ($filteredcourses) {
return in_array($course, $filteredcourses);
});
$allusercourses = array_merge($allusercourses, $filteredcourses);
// Flag to indicate whether the query needs to exclude group overrides.
$viewgroupsonly = false;
if ($user) {
// Set filter condition for the user's events.
// Even though $user is a single scalar, we still use get_in_or_equal() because we are inside a loop.
list($inusers, $inuserparams) = $DB->get_in_or_equal($user, SQL_PARAMS_NAMED);
$condition = "(ev.userid $inusers AND ev.courseid = 0 AND ev.groupid = 0 AND ev.categoryid = 0)";
$subqueryconditions[] = $condition;
$subqueryparams = array_merge($subqueryparams, $inuserparams);
foreach ($usercourses as $courseid) {
if (has_capability('moodle/site:accessallgroups', \context_course::instance($courseid), $userrecord)) {
$usergroupmembership = groups_get_all_groups($courseid, $user, 0, 'g.id');
if (count($usergroupmembership) == 0) {
$viewgroupsonly = true;
break;
}
}
}
}
// Set filter condition for the user's group events.
if ($usergroups === true || $viewgroupsonly) {
// Fetch group events, but not group overrides.
$groupconditions = "(ev.groupid != 0 AND ev.eventtype = 'group')";
} else if (!empty($usergroups)) {
// Fetch group events and group overrides.
list($inusergroups, $inusergroupparams) = $DB->get_in_or_equal($usergroups, SQL_PARAMS_NAMED);
$groupconditions = "(ev.groupid $inusergroups)";
$subqueryparams = array_merge($subqueryparams, $inusergroupparams);
}
}
} else if ($users === true) {
// Events from ALL users.
$subqueryconditions[] = "(ev.userid != 0 AND ev.courseid = 0 AND ev.groupid = 0 AND ev.categoryid = 0)";
if (is_array($groups)) {
// Events from a number of groups.
list($insqlgroups, $inparamsgroups) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED);
$subqueryconditions[] = "ev.groupid $insqlgroups";
$subqueryparams = array_merge($subqueryparams, $inparamsgroups);
} else if ($groups === true) {
// Events from ALL groups.
$subqueryconditions[] = "ev.groupid != 0";
}
if ($courses === true) {
// ALL course events. It's not needed to worry about users' access as $users = true.
$subqueryconditions[] = "(ev.groupid = 0 AND ev.courseid != 0 AND ev.categoryid = 0)";
}
}
// Get courses to be used for the subquery.
$subquerycourses = [];
if (is_array($courses)) {
$subquerycourses = $courses;
}
// Merge with user courses, if necessary.
if (!empty($allusercourses)) {
$subquerycourses = array_merge($subquerycourses, $allusercourses);
// Make sure we remove duplicate values.
$subquerycourses = array_unique($subquerycourses);
}
// Set subquery filter condition for the courses.
if (!empty($subquerycourses)) {
list($incourses, $incoursesparams) = $DB->get_in_or_equal($subquerycourses, SQL_PARAMS_NAMED);
if (isset($groupconditions)) {
$groupconditions = $groupconditions." OR ";
} else {
$groupconditions = '';
}
$condition = "($groupconditions(ev.groupid = 0 AND ev.courseid $incourses AND ev.categoryid = 0))";
$subtimesparams = [];
if (!empty($subquerytimeconditions)) {
$subtimes = $this->subquerytimeconditions("courses", $subquerytimeconditions, $whereparams);
$condition .= $subtimes['where'];
$subtimesparams = $subtimes['params'];
}
$subqueryconditions[] = $condition;
$subqueryparams = array_merge($subqueryparams, $incoursesparams, $subtimesparams);
}
// Set subquery filter condition for the categories.
if ($categories === true) {
$subqueryconditions[] = "(ev.categoryid != 0 AND ev.eventtype = 'category')";
} else if (!empty($categories)) {
list($incategories, $incategoriesparams) = $DB->get_in_or_equal($categories, SQL_PARAMS_NAMED);
$condition = "(ev.groupid = 0 AND ev.courseid = 0 AND ev.categoryid $incategories)";
$subtimesparams = [];
if (!empty($subquerytimeconditions)) {
$subtimes = $this->subquerytimeconditions("cats", $subquerytimeconditions, $whereparams);
$condition .= $subtimes['where'];
$subtimesparams = $subtimes['params'];
}
$subqueryconditions[] = $condition;
$subqueryparams = array_merge($subqueryparams, $incategoriesparams, $subtimesparams);
}
// Build the WHERE condition for the sub-query.
if (!empty($subqueryconditions)) {
$unionstartquery = "SELECT modulename, instance, eventtype, priority
FROM {event} ev
WHERE ";
$subqueryunion = '('.$unionstartquery . implode(" UNION $unionstartquery ", $subqueryconditions).')';
} else {
$subqueryunion = '{event}';
}
// Merge subquery parameters to the parameters of the main query.
if (!empty($subqueryparams)) {
$params = array_merge($params, $subqueryparams);
}
// Sub-query that fetches the list of unique events that were filtered based on priority.
$subquery = "SELECT ev.modulename,
ev.instance,
ev.eventtype,
MIN(ev.priority) as priority
FROM $subqueryunion ev
GROUP BY ev.modulename, ev.instance, ev.eventtype";
// Build the main query.
$sql = "SELECT e.*, c.fullname AS coursefullname, c.shortname AS courseshortname
FROM {event} e
INNER JOIN ($subquery) fe
ON e.modulename = fe.modulename
AND e.instance = fe.instance
AND e.eventtype = fe.eventtype
AND (e.priority = fe.priority OR (e.priority IS NULL AND fe.priority IS NULL))
LEFT JOIN {modules} m
ON e.modulename = m.name
LEFT JOIN {course} c
ON c.id = e.courseid
WHERE (m.visible = 1 OR m.visible IS NULL) AND $whereclause
ORDER BY " . ($ordersql ? $ordersql : "e.timestart");
if (!empty($whereparams)) {
$params = array_merge($params, $whereparams);
}
$events = $DB->get_records_sql($sql, $params, $offset, $limitnum);
return $events === false ? [] : $events;
}
/**
* Returns a query fragment and params, with time constraints applied
*
* @param string $prefix
* @param array $conditions
* @param array $params
* @return array [<where>, <params>]
*/
protected function subquerytimeconditions(string $prefix, array $conditions, array $params): array {
$outwhere = '';
$outparams = [];
// Most specific to least specific.
$timeparams = ['timefromid', 'timefrom3', 'timefrom2', 'timefrom1', 'timefrom', 'timetoid', 'timeto2', 'timeto1', 'timeto'];
$whereconditions = [];
foreach ($conditions as $condition) {
$where = $condition;
// This query has been borrowed from the main WHERE clause, so the alias needs to be renamed to match the union.
$where = str_replace('e.id', 'ev.id', $where);
foreach ($timeparams as $timeparam) {
if (isset($params[$timeparam])) {
$where = str_replace(":{$timeparam}", ":{$prefix}{$timeparam}", $where);
$outparams["{$prefix}{$timeparam}"] = $params[$timeparam];
}
}
$whereconditions[] = $where;
}
if (count($whereconditions) > 0) {
$outwhere = ' AND ' . implode(' AND ', $whereconditions);
}
return ['where' => $outwhere, 'params' => $outparams];
}
}
@@ -0,0 +1,63 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Raw event strategy retrieval interface.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\strategies;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for an raw event retrival strategy class.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface raw_event_retrieval_strategy_interface {
/**
* Retrieve raw calendar event records from the DB.
*
* @param array|null $usersfilter Array of users to retrieve events for.
* @param array|null $groupsfilter Array of groups to retrieve events for.
* @param array|null $coursesfilter Array of courses to retrieve events for.
* @param array|null $categoriesfilter Array of categories to retrieve events for.
* @param array|null $whereconditions Array of where conditions to restrict results.
* @param array|null $whereparams Array of parameters for $whereconditions.
* @param string|null $ordersql SQL to order results.
* @param int|null $offset Amount to offset results by.
* @param int $limitnum Return at most this many results.
* @param bool $ignorehidden True to ignore hidden events. False to include them.
* @return \stdClass[] Array of event records.
*/
public function get_raw_events(
array $usersfilter = null,
array $groupsfilter = null,
array $coursesfilter = null,
array $categoriesfilter = null,
array $whereconditions = null,
array $whereparams = null,
$ordersql = null,
$offset = null,
$limitnum = 40,
$ignorehidden = true
);
}
@@ -0,0 +1,93 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class representing an action a user should take.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\value_objects;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\entities\action_interface;
/**
* Class representing an action a user should take
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class action implements action_interface {
/**
* @var string $name The action's name.
*/
protected $name;
/**
* @var \moodle_url $url The action's URL.
*/
protected $url;
/**
* @var int $itemcount How many items there are to action.
*/
protected $itemcount;
/**
* @var bool $actionable Whether or not the event is currently actionable.
*/
protected $actionable;
/**
* Constructor.
*
* @param string $name The action's name.
* @param \moodle_url $url The action's URL.
* @param int $itemcount How many items there are to action.
* @param bool $actionable Whether or not the event is currently actionable.
*/
public function __construct(
$name,
\moodle_url $url,
$itemcount,
$actionable
) {
$this->name = $name;
$this->url = $url;
$this->itemcount = $itemcount;
$this->actionable = $actionable;
}
public function get_name() {
return $this->name;
}
public function get_url() {
return $this->url;
}
public function get_item_count() {
return $this->itemcount;
}
public function is_actionable() {
return $this->actionable;
}
}
@@ -0,0 +1,49 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Description value object interface.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\value_objects;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for a description value object.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface description_interface {
/**
* Get the description's text.
*
* @return string The description's text.
*/
public function get_value();
/**
* Get the description's format.
*
* @return int The description's format.
*/
public function get_format();
}
@@ -0,0 +1,64 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Description value object.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\value_objects;
defined('MOODLE_INTERNAL') || die();
/**
* Class representing a description value object.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event_description implements description_interface {
/**
* @var string $value The description's text.
*/
protected $value;
/**
* @var int $format The description's format.
*/
protected $format;
/**
* Constructor.
*
* @param string $value The description's value.
* @param int $format The description's format.
*/
public function __construct($value, $format) {
$this->value = $value;
$this->format = $format;
}
public function get_value() {
return $this->value;
}
public function get_format() {
return $this->format;
}
}
@@ -0,0 +1,112 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Event times class.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\value_objects;
defined('MOODLE_INTERNAL') || die();
/**
* Class representing event times.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event_times implements times_interface {
/**
* @var \DateTimeImmutable $start Event start time.
*/
protected $start;
/**
* @var \DateTimeImmutable $end Event end time.
*/
protected $end;
/**
* @var \DateTimeImmutable $sort Time used to sort events.
*/
protected $sort;
/**
* @var \DateTimeImmutable $modified Time event was last modified.
*/
protected $modified;
/**
* @var \DateTimeImmutable $usermidnight User midnight for the event.
*/
protected $usermidnight;
/**
* Constructor.
*
* @param \DateTimeImmutable $start Event start time.
* @param \DateTimeImmutable $end Event end time.
* @param \DateTimeImmutable $sort Date used to sort events.
* @param \DateTimeImmutable $modified Time event was last updated.
* @param \DateTimeImmutable $usermidnight User midnight for the event.
*/
public function __construct(
\DateTimeImmutable $start,
\DateTimeImmutable $end,
\DateTimeImmutable $sort,
\DateTimeImmutable $modified,
\DateTimeImmutable $usermidnight
) {
$this->start = $start;
$this->end = $end;
$this->sort = $sort;
$this->modified = $modified;
$this->usermidnight = $usermidnight;
}
public function get_start_time() {
return $this->start;
}
public function get_end_time() {
return $this->end;
}
public function get_duration() {
return $this->end->diff($this->start);
}
public function get_modified_time() {
return $this->modified;
}
public function get_sort_time() {
return $this->sort;
}
/**
* Getter for usermidnight.
*
* @return \DateTimeImmutable
*/
public function get_usermidnight_time() {
return $this->usermidnight;
}
}
@@ -0,0 +1,77 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Times interface.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\value_objects;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for various times.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface times_interface {
/**
* Get the start time.
*
* @return \DateTimeImmutable
*/
public function get_start_time();
/**
* Get the end time.
*
* @return \DateTimeImmutable
*/
public function get_end_time();
/**
* Get the duration (the time between start and end).
*
* @return \DateInterval
*/
public function get_duration();
/**
* Get the sort time.
*
* @return \DateTimeImmutable
*/
public function get_sort_time();
/**
* Get the modified time.
*
* @return \DateTimeImmutable
*/
public function get_modified_time();
/**
* Get the user midnight time.
*
* @return \DateTimeImmutable
*/
public function get_usermidnight_time();
}