MDL-58866 core_calendar: allow component events

This commit is contained in:
Marina Glancy 2020-04-29 22:22:10 +02:00
parent 1afe68f382
commit 1a972b06f1
30 changed files with 201 additions and 71 deletions

View File

@ -56,7 +56,7 @@
<small class="text-muted text-truncate mb-0">{{#quote}}{{{course.fullnamedisplay}}}{{/quote}}</small>
{{#action.actionable}}
<h6 class="mb-0 pt-2">
<a href="{{{action.url}}}" aria-label="{{{action.name}}}" title="{{{action.name}}}" class="list-group-item-action">{{{action.name}}}</a>
<a href="{{action.url}}" aria-label="{{action.name}}" title="{{action.name}}" class="list-group-item-action">{{{action.name}}}</a>
{{#action.showitemcount}}
<span class="badge badge-secondary">{{action.itemcount}}</span>
{{/action.showitemcount}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -403,7 +403,8 @@ const renderEventSummaryModal = (eventId) => {
candelete: eventData.candelete,
headerclasses: getEventTypeClassFromType(eventData.normalisedeventtype),
isactionevent: eventData.isactionevent,
url: eventData.url
url: eventData.url,
action: eventData.action
}
};

View File

@ -109,7 +109,6 @@ class calendar_event_exporter extends event_exporter_base {
} else if ($event->get_type() == 'category') {
$url = $event->get_category()->get_proxied_instance()->get_view_link();
} else {
// TODO MDL-58866 We do not have any way to find urls for events outside of course modules.
$url = course_get_url($hascourse ? $course : SITEID);
}

View File

@ -49,7 +49,7 @@ class event_action_exporter extends exporter {
public function __construct(action_interface $action, $related = []) {
$data = new \stdClass();
$data->name = $action->get_name();
$data->url = $action->get_url()->out(true);
$data->url = $action->get_url()->out(false);
$data->itemcount = $action->get_item_count();
$data->actionable = $action->is_actionable();
@ -90,17 +90,14 @@ class event_action_exporter extends exporter {
protected function get_other_values(renderer_base $output) {
$event = $this->related['event'];
if (!$event->get_course_module()) {
// TODO MDL-58866 Only activity modules currently support this callback.
if (!$event->get_component()) {
return ['showitemcount' => false];
}
$modulename = $event->get_course_module()->get('modname');
$component = 'mod_' . $modulename;
$showitemcountcallback = 'core_calendar_event_action_shows_item_count';
$mapper = container::get_event_mapper();
$calevent = $mapper->from_event_to_legacy_event($event);
$params = [$calevent, $this->data->itemcount];
$showitemcount = component_callback($component, $showitemcountcallback, $params, false);
$showitemcount = component_callback($event->get_component(), $showitemcountcallback, $params, false);
// Prepare other values data.
$data = [
@ -120,4 +117,13 @@ class event_action_exporter extends exporter {
'event' => '\\core_calendar\\local\\event\\entities\\event_interface'
];
}
/**
* Magic method returning parameters for formatting 'name' property
*
* @return bool[]
*/
protected function get_format_parameters_for_name() {
return ['escape' => false];
}
}

View File

@ -51,10 +51,6 @@ class event_exporter extends event_exporter_base {
$values = parent::define_other_properties();
$values['url'] = ['type' => PARAM_URL];
$values['action'] = [
'type' => event_action_exporter::read_properties_definition(),
'optional' => true,
];
return $values;
}
@ -86,7 +82,6 @@ class event_exporter extends event_exporter_base {
} else if ($event->get_type() == 'course') {
$url = \course_get_url($this->related['course'] ?: SITEID);
} else {
// TODO MDL-58866 We do not have any way to find urls for events outside of course modules.
$url = \course_get_url($this->related['course'] ?: SITEID);
}
$values['url'] = $url->out(false);
@ -95,17 +90,6 @@ class event_exporter extends event_exporter_base {
$legacyevent = container::get_event_mapper()->from_event_to_legacy_event($event);
$values['formattedtime'] = calendar_format_event_time($legacyevent, time(), null, false);
if ($event instanceof action_event_interface) {
$actionrelated = [
'context' => $context,
'event' => $event
];
$actionexporter = new event_action_exporter($event->get_action(), $actionrelated);
$values['action'] = $actionexporter->export($output);
}
return $values;
}
}

View File

@ -89,6 +89,7 @@ class event_exporter_base extends exporter {
$data->timesort = $event->get_times()->get_sort_time()->getTimestamp();
$data->visible = $event->is_visible() ? 1 : 0;
$data->timemodified = $event->get_times()->get_modified_time()->getTimestamp();
$data->component = $event->get_component();
if ($repeats = $event->get_repeats()) {
$data->repeatid = $repeats->get_id();
@ -160,6 +161,12 @@ class event_exporter_base extends exporter {
'default' => null,
'null' => NULL_ALLOWED
],
'component' => [
'type' => PARAM_COMPONENT,
'optional' => true,
'default' => null,
'null' => NULL_ALLOWED
],
'modulename' => [
'type' => PARAM_TEXT,
'optional' => true,
@ -242,6 +249,10 @@ class event_exporter_base extends exporter {
'normalisedeventtypetext' => [
'type' => PARAM_TEXT
],
'action' => [
'type' => event_action_exporter::read_properties_definition(),
'optional' => true,
],
];
}
@ -291,7 +302,7 @@ class event_exporter_base extends exporter {
$values['category'] = $categorysummaryexporter->export($output);
}
if ($course) {
if ($course && $course->id != SITEID) {
$coursesummaryexporter = new course_summary_exporter($course, ['context' => $context]);
$values['course'] = $coursesummaryexporter->export($output);
}
@ -319,6 +330,16 @@ class event_exporter_base extends exporter {
['context' => \context_course::instance($event->get_course()->get('id'))]);
}
if ($event instanceof action_event_interface) {
// Export event action if applicable.
$actionrelated = [
'context' => $this->related['context'],
'event' => $event
];
$actionexporter = new event_action_exporter($event->get_action(), $actionrelated);
$values['action'] = $actionexporter->export($output);
}
return $values;
}

View File

@ -279,7 +279,7 @@ class container {
// of the event class.
$mapper = self::$eventmapper;
$action = null;
if ($event->get_course_module()) {
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,
@ -288,10 +288,9 @@ class container {
$legacyevent->userid = $requestinguserid;
}
// TODO MDL-58866 Only activity modules currently support this callback.
// Any other event will not be displayed on the dashboard.
$action = component_callback(
'mod_' . $event->get_course_module()->get('modname'),
$event->get_component(),
'core_calendar_provide_event_action',
[
$legacyevent,
@ -322,7 +321,7 @@ class container {
public static function apply_component_is_event_visible(event_interface $event) {
$mapper = self::$eventmapper;
$eventvisible = null;
if ($event->get_course_module()) {
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,
@ -331,9 +330,8 @@ class container {
$legacyevent->userid = $requestinguserid;
}
// TODO MDL-58866 Only activity modules currently support this callback.
$eventvisible = component_callback(
'mod_' . $event->get_course_module()->get('modname'),
$event->get_component(),
'core_calendar_is_event_visible',
[
$legacyevent,

View File

@ -125,4 +125,12 @@ class action_event implements action_event_interface {
public function get_action() {
return $this->action;
}
/**
* Event component
* @return string
*/
public function get_component() {
return $this->event->get_component();
}
}

View File

@ -102,6 +102,11 @@ class event implements event_interface {
*/
protected $visible;
/**
* @var string $component
*/
protected $component;
/**
* @var proxy_interface $subscription Subscription for this event.
*/
@ -124,6 +129,7 @@ class event implements event_interface {
* @param bool $visible The event's visibility. True for visible, false for invisible.
* @param proxy_interface $subscription The event's subscription.
* @param string $location The event's location.
* @param string $component The event's component.
*/
public function __construct(
$id,
@ -139,7 +145,8 @@ class event implements event_interface {
times_interface $times,
$visible,
proxy_interface $subscription = null,
$location = null
$location = null,
$component = null
) {
$this->id = $id;
$this->name = $name;
@ -155,6 +162,7 @@ class event implements event_interface {
$this->times = $times;
$this->visible = $visible;
$this->subscription = $subscription;
$this->component = $component;
}
public function get_id() {
@ -212,4 +220,12 @@ class event implements event_interface {
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;
}
}

View File

@ -133,4 +133,10 @@ interface event_interface {
* @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();
}

View File

@ -133,6 +133,7 @@ abstract class event_abstract_factory implements event_factory_interface {
$user = null;
$module = null;
$subscription = null;
$component = null;
if ($dbrow->modulename && $dbrow->instance) {
$module = new cm_info_proxy($dbrow->modulename, $dbrow->instance, $dbrow->courseid);
@ -171,6 +172,10 @@ abstract class event_abstract_factory implements event_factory_interface {
$repeatcollection = null;
}
if (!empty($dbrow->component)) {
$component = $dbrow->component;
}
$event = new event(
$dbrow->id,
$dbrow->name,
@ -190,7 +195,8 @@ abstract class event_abstract_factory implements event_factory_interface {
),
!empty($dbrow->visible),
$subscription,
$dbrow->location
$dbrow->location,
$component
);
$isactionevent = !empty($dbrow->type) && $dbrow->type == CALENDAR_EVENT_TYPE_ACTION;

View File

@ -75,6 +75,7 @@ class event_mapper implements event_mapper_interface {
'groupid' => $coalesce('groupid'),
'userid' => $coalesce('userid'),
'repeatid' => $coalesce('repeatid'),
'component' => $coalesce('component'),
'modulename' => $coalesce('modulename'),
'instance' => $coalesce('instance'),
'eventtype' => $coalesce('eventtype'),
@ -98,6 +99,7 @@ class event_mapper implements event_mapper_interface {
$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;
@ -127,6 +129,7 @@ class event_mapper implements event_mapper_interface {
'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(),

View File

@ -797,20 +797,21 @@ class core_calendar_external extends external_api {
self::validate_context($context);
$warnings = array();
$legacyevent = calendar_event::load($eventid);
// Must check we can see this event.
if (!calendar_view_event_allowed($legacyevent)) {
$eventvault = event_container::get_event_vault();
if ($event = $eventvault->get_event_by_id($eventid)) {
$mapper = event_container::get_event_mapper();
if (!calendar_view_event_allowed($mapper->from_event_to_legacy_event($event))) {
$event = null;
}
}
if (!$event) {
// We can't return a warning in this case because the event is not optional.
// We don't know the context for the event and it's not worth loading it.
$syscontext = context_system::instance();
throw new \required_capability_exception($syscontext, 'moodle/course:view', 'nopermission', '');
}
$legacyevent->count_repeats();
$eventmapper = event_container::get_event_mapper();
$event = $eventmapper->from_legacy_event_to_event($legacyevent);
$cache = new events_related_objects_cache([$event]);
$relatedobjects = [
'context' => $cache->get_context($event),

View File

@ -172,6 +172,7 @@ define('CALENDAR_EVENT_TYPE_ACTION', 1);
* @property int $userid The user the event is associated with (0 if none)
* @property int $repeatid If this is a repeated event this will be set to the
* id of the original
* @property string $component If created by a plugin/component (other than module), the full frankenstyle name of a component
* @property string $modulename If added by a module this will be the module name
* @property int $instance If added by a module this will be the module instance
* @property string $eventtype The event type
@ -257,6 +258,10 @@ class calendar_event {
$data->format = editors_get_preferred_format();
}
if (empty($data->component)) {
$data->component = null;
}
$this->properties = $data;
}
@ -337,6 +342,7 @@ class calendar_event {
$context = \context_user::instance($this->properties->userid);
} else if (isset($this->properties->userid) && $this->properties->userid > 0
&& $this->properties->userid != $USER->id &&
!empty($this->properties->modulename) &&
isset($this->properties->instance) && $this->properties->instance > 0) {
$cm = get_coursemodule_from_instance($this->properties->modulename, $this->properties->instance, 0,
false, MUST_EXIST);
@ -1523,10 +1529,13 @@ function calendar_get_group_cached($groupid) {
/**
* Add calendar event metadata
*
* @deprecated since 3.9
*
* @param stdClass $event event info
* @return stdClass $event metadata
*/
function calendar_add_event_metadata($event) {
debugging('This function is no longer used', DEBUG_DEVELOPER);
global $CFG, $OUTPUT;
// Support multilang in event->name.
@ -2276,6 +2285,10 @@ function calendar_edit_event_allowed($event, $manualedit = false) {
return has_capability('moodle/course:manageactivities', $context);
}
if ($manualedit && !empty($event->component)) {
return false;
}
// You cannot edit URL based calendar subscription events presently.
if (!empty($event->subscriptionid)) {
if (!empty($event->subscription->url)) {
@ -2324,8 +2337,8 @@ function calendar_edit_event_allowed($event, $manualedit = false) {
* @return bool Whether the user has permission to delete the event or not.
*/
function calendar_delete_event_allowed($event) {
// Only allow delete if you have capabilities and it is not an module event.
return (calendar_edit_event_allowed($event) && empty($event->modulename));
// Only allow delete if you have capabilities and it is not an module or component event.
return (calendar_edit_event_allowed($event) && empty($event->modulename) && empty($event->component));
}
/**

View File

@ -135,12 +135,15 @@ class core_calendar_renderer extends plugin_renderer_base {
/**
* Displays an event
*
* @deprecated since 3.9
*
* @param calendar_event $event
* @param bool $showactions
* @return string
*/
public function event(calendar_event $event, $showactions=true) {
global $CFG;
debugging('This function is no longer used', DEBUG_DEVELOPER);
$event = calendar_add_event_metadata($event);
$context = $event->context;

View File

@ -34,6 +34,7 @@
{
"formattedtime": "Wednesday, 17 April, 9:27 AM",
"normalisedeventtype": "Group",
"normalisedeventtypetext": "Group event",
"description": "An random event description",
"location": "Moodle HQ",
"isactionevent": "true",
@ -77,29 +78,19 @@
<div class="location-content col-11">{{{.}}}</div>
</div>
{{/location}}
{{#isactionevent}}
<div class="row mt-1">
<div class="col-1">{{#pix}} i/courseevent, core, {{#str}} course {{/str}} {{/pix}}</div>
<div class="col-11"><a href="{{course.viewurl}}">{{{course.fullname}}}</a></div>
</div>
{{/isactionevent}}
{{#iscategoryevent}}
<div class="row mt-1">
<div class="col-1">{{#pix}} i/categoryevent, core, {{#str}} category {{/str}} {{/pix}}</div>
<div class="col-11">{{{category.nestedname}}}</div>
</div>
{{/iscategoryevent}}
{{#iscourseevent}}
{{#course.viewurl}}
<div class="row mt-1">
<div class="col-1">{{#pix}} i/courseevent, core, {{#str}} course {{/str}} {{/pix}}</div>
<div class="col-11"><a href="{{url}}">{{{course.fullname}}}</a></div>
<div class="col-11"><a href="{{course.viewurl}}">{{{course.fullname}}}</a></div>
</div>
{{/iscourseevent}}
{{/course.viewurl}}
{{#groupname}}
<div class="row mt-1">
<div class="col-1">{{#pix}} i/courseevent, core, {{#str}} course {{/str}} {{/pix}}</div>
<div class="col-11"><a href="{{url}}">{{{course.fullname}}}</a></div>
</div>
<div class="row mt-1">
<div class="col-1">{{#pix}} i/groupevent, core, {{#str}} group {{/str}} {{/pix}}</div>
<div class="col-11">{{{groupname}}}</div>

View File

@ -80,10 +80,17 @@
<div class="description card-body">
{{> core_calendar/event_details }}
</div>
{{#isactionevent}}
{{#action.actionable}}
<div class="card-footer text-right bg-transparent">
<a href="{{action.url}}" class="card-link">{{{action.name}}}</a>
</div>
{{/action.actionable}}
{{^action.actionable}}
{{#isactionevent}}
<div class="card-footer text-right bg-transparent">
<a href="{{url}}" class="card-link">{{#str}} gotoactivity, core_calendar {{/str}}</a>
</div>
{{/isactionevent}}
{{/isactionevent}}
{{/action.actionable}}
</div>
</div>

View File

@ -21,7 +21,7 @@
Example context (json):
{
"title": "Assignment due 1",
"title": "Assignment due 1"
}
}}
{{< core/modal }}
@ -29,9 +29,15 @@
{{#candelete}}
<button type="button" class="btn btn-secondary" data-action="delete">{{#str}} delete {{/str}}</button>
{{/candelete}}
{{#isactionevent}}
{{#action.actionable}}
<a href="{{action.url}}">{{{action.name}}}</a>
{{/action.actionable}}
{{^action.actionable}}
{{#isactionevent}}
<a href="{{url}}">{{#str}} gotoactivity, core_calendar {{/str}}</a>
{{/isactionevent}}
{{/isactionevent}}
{{/action.actionable}}
{{^isactionevent}}
{{#canedit}}
<button type="button" class="btn btn-primary" data-action="edit">{{#str}} edit {{/str}}</button>

View File

@ -156,6 +156,14 @@ class core_calendar_action_event_test_event implements event_interface {
public function is_visible() {
return true;
}
/**
* Component
* @return string|null
*/
public function get_component() {
return null;
}
}
/**

View File

@ -255,6 +255,14 @@ class event_mapper_test_action_event implements action_event_interface {
true
);
}
/**
* Component
* @return string|null
*/
public function get_component() {
return $this->event->get_component();
}
}
/**
@ -374,6 +382,14 @@ class event_mapper_test_event implements event_interface {
public function is_visible() {
return true;
}
/**
* Component
* @return string|null
*/
public function get_component() {
return null;
}
}
/**

View File

@ -59,16 +59,18 @@ class core_calendar_event_testcase extends advanced_testcase {
$constructorparams['times'],
$constructorparams['visible'],
$constructorparams['subscription'],
$constructorparams['location']
$constructorparams['location'],
$constructorparams['component']
);
foreach ($constructorparams as $name => $value) {
if ($name !== 'visible') {
if ($name !== 'visible' && $name !== 'component') {
$this->assertEquals($event->{'get_' . $name}(), $value);
}
}
$this->assertEquals($event->is_visible(), $constructorparams['visible']);
$this->assertEquals('mod_' . $event->get_course_module()->get('modname'), $event->get_component());
}
/**
@ -76,7 +78,7 @@ class core_calendar_event_testcase extends advanced_testcase {
*/
public function getters_testcases() {
$lamecallable = function($id) {
return (object)['id' => $id];
return (object)['id' => $id, 'modname' => 'assign'];
};
return [
@ -101,6 +103,7 @@ class core_calendar_event_testcase extends advanced_testcase {
'visible' => true,
'subscription' => new std_proxy(1, $lamecallable),
'location' => 'Test',
'component' => null
]
],
];

View File

@ -137,7 +137,8 @@ class action_event_test_factory implements event_factory_interface {
),
!empty($record->visible),
$subscription,
$record->location
$record->location,
!empty($record->component) ? $record->component : null
);
$action = new action(

View File

@ -205,7 +205,8 @@ class core_calendar_repeat_event_collection_event_test_factory implements event_
),
!empty($dbrow->visible),
new std_proxy($dbrow->subscriptionid, $identity),
$dbrow->location
$dbrow->location,
$dbrow->component
);
}
}

View File

@ -1,6 +1,11 @@
This files describes API changes in /calendar/* ,
information provided here is intended especially for developers.
=== 3.9 ===
* Plugins can now create their own calendar events, both standard and action ones. To do it they need to specify
$event->component when creating an event. Component events can not be edited or deleted manually.
See https://docs.moodle.org/dev/Calendar_API#Component_events
=== 3.8 ===
* The following functions have been finally deprecated and can not be used anymore:
* calendar_wday_name()

View File

@ -206,6 +206,7 @@ function uninstall_plugin($type, $name) {
// delete calendar events
$DB->delete_records('event', array('modulename' => $pluginname));
$DB->delete_records('event', ['component' => $component]);
// Delete scheduled tasks.
$DB->delete_records('task_scheduled', array('component' => $component));

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="lib/db" VERSION="20200415" COMMENT="XMLDB file for core Moodle tables"
<XMLDB PATH="lib/db" VERSION="20200504" COMMENT="XMLDB file for core Moodle tables"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
>
@ -433,6 +433,7 @@
<FIELD NAME="groupid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="repeatid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false" COMMENT="Component that created this event, if specified, only component itself can edit and delete it"/>
<FIELD NAME="modulename" TYPE="char" LENGTH="20" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="instance" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="type" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
@ -463,6 +464,7 @@
<INDEX NAME="groupid-courseid-categoryid-visible-userid" UNIQUE="false" FIELDS="groupid, courseid, categoryid, visible, userid" COMMENT="used for calendar view"/>
<INDEX NAME="eventtype" UNIQUE="false" FIELDS="eventtype"/>
<INDEX NAME="modulename-instance" UNIQUE="false" FIELDS="modulename, instance"/>
<INDEX NAME="component" UNIQUE="false" FIELDS="component, eventtype, instance"/>
</INDEXES>
</TABLE>
<TABLE NAME="cache_filters" COMMENT="For keeping information about cached data">

View File

@ -2314,5 +2314,29 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2020042800.01);
}
if ($oldversion < 2020051900.01) {
// Define field component to be added to event.
$table = new xmldb_table('event');
$field = new xmldb_field('component', XMLDB_TYPE_CHAR, '100', null, null, null, null, 'repeatid');
// Conditionally launch add field component.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Define index component (not unique) to be added to event.
$table = new xmldb_table('event');
$index = new xmldb_index('component', XMLDB_INDEX_NOTUNIQUE, ['component', 'eventtype', 'instance']);
// Conditionally launch add index component.
if (!$dbman->index_exists($table, $index)) {
$dbman->add_index($table, $index);
}
// Main savepoint reached.
upgrade_main_savepoint(true, 2020051900.01);
}
return true;
}

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2020051900.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2020051900.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '3.9dev+ (Build: 20200519)'; // Human-friendly version name