diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php
index 577b909a22d..d1324de88d3 100644
--- a/backup/moodle2/backup_stepslib.php
+++ b/backup/moodle2/backup_stepslib.php
@@ -275,7 +275,7 @@ class backup_module_structure_step extends backup_structure_step {
'visibleold', 'groupmode', 'groupingid',
'completion', 'completiongradeitemnumber', 'completionpassgrade',
'completionview', 'completionexpected',
- 'availability', 'showdescription'));
+ 'availability', 'showdescription', 'downloadcontent'));
$tags = new backup_nested_element('tags');
$tag = new backup_nested_element('tag', array('id'), array('name', 'rawname'));
diff --git a/course/externallib.php b/course/externallib.php
index 0ae4854ca0c..42a2369e245 100644
--- a/course/externallib.php
+++ b/course/externallib.php
@@ -275,6 +275,7 @@ class core_course_external extends external_api {
$module['afterlink'] = $cm->afterlink;
$module['customdata'] = json_encode($cm->customdata);
$module['completion'] = $cm->completion;
+ $module['downloadcontent'] = $cm->downloadcontent;
$module['noviewlink'] = plugin_supports('mod', $cm->modname, FEATURE_NO_VIEW_LINK, false);
$module['dates'] = $activitydates;
@@ -477,6 +478,7 @@ class core_course_external extends external_api {
'completion' => new external_value(PARAM_INT, 'Type of completion tracking:
0 means none, 1 manual, 2 automatic.', VALUE_OPTIONAL),
'completiondata' => $completiondefinition,
+ 'downloadcontent' => new external_value(PARAM_INT, 'The download content value', VALUE_OPTIONAL),
'dates' => new external_multiple_structure(
new external_single_structure(
array(
@@ -2828,6 +2830,7 @@ class core_course_external extends external_api {
$info->groupmode = $cm->groupmode;
$info->groupingid = $cm->groupingid;
$info->completion = $cm->completion;
+ $info->downloadcontent = $cm->downloadcontent;
}
// Format name.
$info->name = external_format_string($cm->name, $context->id);
@@ -2871,6 +2874,7 @@ class core_course_external extends external_api {
'completionview' => new external_value(PARAM_INT, 'Completion view setting', VALUE_OPTIONAL),
'completionexpected' => new external_value(PARAM_INT, 'Completion time expected', VALUE_OPTIONAL),
'showdescription' => new external_value(PARAM_INT, 'If the description is showed', VALUE_OPTIONAL),
+ 'downloadcontent' => new external_value(PARAM_INT, 'The download content value', VALUE_OPTIONAL),
'availability' => new external_value(PARAM_RAW, 'Availability settings', VALUE_OPTIONAL),
'grade' => new external_value(PARAM_FLOAT, 'Grade (max value or scale id)', VALUE_OPTIONAL),
'scale' => new external_value(PARAM_TEXT, 'Scale items (if used)', VALUE_OPTIONAL),
diff --git a/course/lib.php b/course/lib.php
index 43733242a18..13091f1f194 100644
--- a/course/lib.php
+++ b/course/lib.php
@@ -467,7 +467,7 @@ function get_array_of_activities($courseid) {
$mod[$seq]->showdescription = $rawmods[$seq]->showdescription;
$mod[$seq]->availability = $rawmods[$seq]->availability;
$mod[$seq]->deletioninprogress = $rawmods[$seq]->deletioninprogress;
-
+ $mod[$seq]->downloadcontent = $rawmods[$seq]->downloadcontent;
$modname = $mod[$seq]->mod;
$functionname = $modname."_get_coursemodule_info";
@@ -852,6 +852,23 @@ function set_coursemodule_idnumber($id, $idnumber) {
return ($cm->idnumber != $idnumber);
}
+/**
+ * Set downloadcontent value to course module.
+ *
+ * @param int $id The id of the module.
+ * @param bool $downloadcontent Whether the module can be downloaded when download course content is enabled.
+ * @return bool True if downloadcontent has been updated, false otherwise.
+ */
+function set_downloadcontent(int $id, bool $downloadcontent): bool {
+ global $DB;
+ $cm = $DB->get_record('course_modules', ['id' => $id], 'id, course, downloadcontent', MUST_EXIST);
+ if ($cm->downloadcontent != $downloadcontent) {
+ $DB->set_field('course_modules', 'downloadcontent', $downloadcontent, ['id' => $cm->id]);
+ rebuild_course_cache($cm->course, true);
+ }
+ return ($cm->downloadcontent != $downloadcontent);
+}
+
/**
* Set the visibility of a module and inherent properties.
*
diff --git a/course/modlib.php b/course/modlib.php
index 6e18ea6facf..c2b3bd8c8eb 100644
--- a/course/modlib.php
+++ b/course/modlib.php
@@ -67,6 +67,9 @@ function add_moduleinfo($moduleinfo, $course, $mform = null) {
if (isset($moduleinfo->cmidnumber)) {
$newcm->idnumber = $moduleinfo->cmidnumber;
}
+ if (isset($moduleinfo->downloadcontent)) {
+ $newcm->downloadcontent = $moduleinfo->downloadcontent;
+ }
$newcm->groupmode = $moduleinfo->groupmode;
$newcm->groupingid = $moduleinfo->groupingid;
$completion = new completion_info($course);
@@ -460,6 +463,10 @@ function set_moduleinfo_defaults($moduleinfo) {
$moduleinfo->visibleoncoursepage = 1;
}
+ if (!isset($moduleinfo->downloadcontent)) {
+ $moduleinfo->downloadcontent = DOWNLOAD_COURSE_CONTENT_ENABLED;
+ }
+
return $moduleinfo;
}
@@ -662,6 +669,10 @@ function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
set_coursemodule_idnumber($moduleinfo->coursemodule, $moduleinfo->cmidnumber);
}
+ if (isset($moduleinfo->downloadcontent)) {
+ set_downloadcontent($moduleinfo->coursemodule, $moduleinfo->downloadcontent);
+ }
+
// Update module tags.
if (core_tag_tag::is_enabled('core', 'course_modules') && isset($moduleinfo->tags)) {
core_tag_tag::set_item_tags('core', 'course_modules', $moduleinfo->coursemodule, $modcontext, $moduleinfo->tags);
@@ -731,6 +742,7 @@ function get_moduleinfo_data($cm, $course) {
$data->completionpassgrade = $cm->completionpassgrade;
$data->completiongradeitemnumber = $cm->completiongradeitemnumber;
$data->showdescription = $cm->showdescription;
+ $data->downloadcontent = $cm->downloadcontent;
$data->tags = core_tag_tag::get_item_tags_array('core', 'course_modules', $cm->id);
if (!empty($CFG->enableavailability)) {
$data->availabilityconditionsjson = $cm->availability;
@@ -830,6 +842,7 @@ function prepare_new_moduleinfo_data($course, $modulename, $section) {
$data->id = '';
$data->instance = '';
$data->coursemodule = '';
+ $data->downloadcontent = DOWNLOAD_COURSE_CONTENT_ENABLED;
// Apply completion defaults.
$defaults = \core_completion\manager::get_default_completion($course, $module);
diff --git a/course/moodleform_mod.php b/course/moodleform_mod.php
index d9018c16bb4..88d84601031 100644
--- a/course/moodleform_mod.php
+++ b/course/moodleform_mod.php
@@ -27,6 +27,7 @@ require_once($CFG->libdir.'/completionlib.php');
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->libdir.'/plagiarismlib.php');
+use core\content\export\exporters\component_exporter;
use core_grades\component_gradeitems;
/**
@@ -637,6 +638,22 @@ abstract class moodleform_mod extends moodleform {
$mform->addHelpButton('groupmode', 'groupmode', 'group');
}
+ if ($CFG->downloadcoursecontentallowed) {
+ $choices = [
+ DOWNLOAD_COURSE_CONTENT_DISABLED => get_string('no'),
+ DOWNLOAD_COURSE_CONTENT_ENABLED => get_string('yes'),
+ ];
+ $mform->addElement('select', 'downloadcontent', get_string('downloadcontent', 'course'), $choices);
+ $downloadcontentdefault = $this->_cm->downloadcontent ?? DOWNLOAD_COURSE_CONTENT_ENABLED;
+ $mform->addHelpButton('downloadcontent', 'downloadcontent', 'course');
+ if (has_capability('moodle/course:configuredownloadcontent', $this->get_context())) {
+ $mform->setDefault('downloadcontent', $downloadcontentdefault);
+ } else {
+ $mform->hardFreeze('downloadcontent');
+ $mform->setConstant('downloadcontent', $downloadcontentdefault);
+ }
+ }
+
if ($this->_features->groupings) {
// Groupings selector - used to select grouping for groups in activity.
$options = array();
diff --git a/course/tests/behat/course_download_content_cm.feature b/course/tests/behat/course_download_content_cm.feature
new file mode 100644
index 00000000000..3f84f88cdc3
--- /dev/null
+++ b/course/tests/behat/course_download_content_cm.feature
@@ -0,0 +1,66 @@
+@core @core_course
+Feature: Activities content download can be controlled
+ In order to allow or restrict access to download activity content
+ As a teacher
+ I can disable the content download of an activity
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ | student1 | Student | 1 | student1@example.com |
+ | manager1 | Manager | 1 | manager1@example.com |
+ And the following "courses" exist:
+ | fullname | shortname |
+ | Course 1 | C1 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ | manager1 | C1 | manager |
+ And the following "activities" exist:
+ | activity | name | intro | introformat | course |
+ | page | Page1 | PageDesc1 | 1 | C1 |
+ And the following "activities" exist:
+ | activity | name | intro | introformat | course | downloadcontent |
+ | folder | Folder1 | FolderDesc1 | 1 | C1 | 0 |
+ And I log in as "admin"
+ And the following config values are set as admin:
+ | downloadcoursecontentallowed | 1 |
+ And I log out
+
+ Scenario: "Include in course downloads (if that feature is enabled)" field default is set to "Yes" if nothing has been set
+ Given I am on the Page1 "Page Activity editing" page logged in as admin
+ Then the field "Include in course downloads (if that feature is enabled)" matches value "Yes"
+
+ Scenario: "Include in course downloads (if that feature is enabled)" field is not visible if course content is disabled on site level
+ Given I log in as "admin"
+ And the following config values are set as admin:
+ | downloadcoursecontentallowed | 0 |
+ And I am on the Page1 "Page Activity editing" page
+ Then "Include in course downloads (if that feature is enabled)" "select" should not exist
+
+ Scenario: "Include in course downloads (if that feature is enabled)" field is visible even if course content is disabled on course level
+ Given I log in as "admin"
+ And I am on "Course 1" course homepage
+ And I navigate to "Settings" in current page administration
+ When I set the field "Enable download course content" to "No"
+ And I press "Save and display"
+ And I am on the Page1 "Page Activity editing" page
+ Then "Include in course downloads (if that feature is enabled)" "select" should exist
+
+ Scenario: "Include in course downloads (if that feature is enabled)" field should be visible but not editable for users without configuredownloadcontent capability
+ Given I log in as "manager1"
+ And I am on the Folder1 "Folder Activity editing" page
+ And "Include in course downloads (if that feature is enabled)" "field" should exist
+ And I log out
+ And I log in as "admin"
+ When I set the following system permissions of "Manager" role:
+ | capability | permission |
+ | moodle/course:configuredownloadcontent | Prohibit |
+ And I log out
+ And I log in as "manager1"
+ And I am on the Folder1 "Folder Activity editing" page
+ Then I should see "Include in course downloads (if that feature is enabled)"
+ And I should see "No"
+ And "Include in course downloads (if that feature is enabled)" "select" should not exist
diff --git a/course/tests/courselib_test.php b/course/tests/courselib_test.php
index 32f66639e07..06229494c3f 100644
--- a/course/tests/courselib_test.php
+++ b/course/tests/courselib_test.php
@@ -7233,4 +7233,30 @@ class core_course_courselib_testcase extends advanced_testcase {
$this->assertEquals(1, average_number_of_participants(true));
}
+ /**
+ * Test the set_downloadcontent() function.
+ */
+ public function test_set_downloadcontent() {
+ $this->resetAfterTest();
+
+ $generator = $this->getDataGenerator();
+ $course = $generator->create_course();
+ $page = $generator->create_module('page', ['course' => $course]);
+
+ // Test the module 'downloadcontent' field is set to enabled.
+ set_downloadcontent($page->cmid, DOWNLOAD_COURSE_CONTENT_ENABLED);
+ $modinfo = get_fast_modinfo($course)->get_cm($page->cmid);
+ $this->assertEquals(DOWNLOAD_COURSE_CONTENT_ENABLED, $modinfo->downloadcontent);
+
+ // Now let's test the 'downloadcontent' value is updated to disabled.
+ set_downloadcontent($page->cmid, DOWNLOAD_COURSE_CONTENT_DISABLED);
+ $modinfo = get_fast_modinfo($course)->get_cm($page->cmid);
+ $this->assertEquals(DOWNLOAD_COURSE_CONTENT_DISABLED, $modinfo->downloadcontent);
+
+ // Nothing to update, the download course content value is the same, it should return false.
+ $this->assertFalse(set_downloadcontent($page->cmid, DOWNLOAD_COURSE_CONTENT_DISABLED));
+
+ // The download course content value has changed, it should return true in this case.
+ $this->assertTrue(set_downloadcontent($page->cmid, DOWNLOAD_COURSE_CONTENT_ENABLED));
+ }
}
diff --git a/course/tests/externallib_test.php b/course/tests/externallib_test.php
index 48650aa6cc7..a2d03e22c4d 100644
--- a/course/tests/externallib_test.php
+++ b/course/tests/externallib_test.php
@@ -1366,6 +1366,28 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertEquals($pagecm->instance, $sections[0]['modules'][0]["instance"]);
}
+ /**
+ * Test get_course_contents returns downloadcontent value.
+ */
+ public function test_get_course_contents_downloadcontent() {
+ $this->resetAfterTest();
+
+ list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
+
+ // Test exclude modules.
+ $sections = core_course_external::get_course_contents($course->id, [
+ ['name' => 'modname', 'value' => 'page'],
+ ['name' => 'modid', 'value' => $pagecm->instance]
+ ]);
+
+ // We need to execute the return values cleaning process to simulate the web service server.
+ $sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
+ $this->assertCount(1, $sections[0]['modules']);
+ $this->assertEquals('page', $sections[0]['modules'][0]['modname']);
+ $this->assertEquals($pagecm->downloadcontent, $sections[0]['modules'][0]['downloadcontent']);
+ $this->assertEquals(DOWNLOAD_COURSE_CONTENT_ENABLED, $sections[0]['modules'][0]['downloadcontent']);
+ }
+
/**
* Test get course contents completion manual
*/
@@ -2373,7 +2395,7 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertCount(0, $result['warnings']);
// Test we retrieve all the fields.
- $this->assertCount(29, $result['cm']);
+ $this->assertCount(30, $result['cm']);
$this->assertEquals($record['name'], $result['cm']['name']);
$this->assertEquals($options['idnumber'], $result['cm']['idnumber']);
$this->assertEquals(100, $result['cm']['grade']);
@@ -2381,6 +2403,7 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertEquals('submissions', $result['cm']['advancedgrading'][0]['area']);
$this->assertEmpty($result['cm']['advancedgrading'][0]['method']);
$this->assertEquals($outcomescale, $result['cm']['outcomes'][0]['scale']);
+ $this->assertEquals(DOWNLOAD_COURSE_CONTENT_ENABLED, $result['cm']['downloadcontent']);
$student = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
@@ -2405,7 +2428,7 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertCount(0, $result['warnings']);
// Test we retrieve only the few files we can see.
- $this->assertCount(11, $result['cm']);
+ $this->assertCount(12, $result['cm']);
$this->assertEquals($assign->cmid, $result['cm']['id']);
$this->assertEquals($course->id, $result['cm']['course']);
$this->assertEquals('assign', $result['cm']['modname']);
@@ -2441,10 +2464,11 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertCount(0, $result['warnings']);
// Test we retrieve all the fields.
- $this->assertCount(27, $result['cm']);
+ $this->assertCount(28, $result['cm']);
$this->assertEquals($record['name'], $result['cm']['name']);
$this->assertEquals($record['grade'], $result['cm']['grade']);
$this->assertEquals($options['idnumber'], $result['cm']['idnumber']);
+ $this->assertEquals(DOWNLOAD_COURSE_CONTENT_ENABLED, $result['cm']['downloadcontent']);
$student = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
@@ -2469,7 +2493,7 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertCount(0, $result['warnings']);
// Test we retrieve only the few files we can see.
- $this->assertCount(11, $result['cm']);
+ $this->assertCount(12, $result['cm']);
$this->assertEquals($quiz->cmid, $result['cm']['id']);
$this->assertEquals($course->id, $result['cm']['course']);
$this->assertEquals('quiz', $result['cm']['modname']);
diff --git a/course/tests/modlib_test.php b/course/tests/modlib_test.php
index 937fcd281fd..473e8a7458d 100644
--- a/course/tests/modlib_test.php
+++ b/course/tests/modlib_test.php
@@ -63,6 +63,8 @@ class core_course_modlib_testcase extends advanced_testcase {
$expecteddata->coursemodule = '';
$expecteddata->advancedgradingmethod_submissions = ''; // Not grading methods enabled by default.
$expecteddata->completion = 0;
+ $expecteddata->downloadcontent = DOWNLOAD_COURSE_CONTENT_ENABLED;
+
// Unset untestable.
unset($data->introeditor);
unset($data->_advancedgradingdata);
@@ -115,6 +117,7 @@ class core_course_modlib_testcase extends advanced_testcase {
$expecteddata->completionpassgrade = $assigncm->completionpassgrade;
$expecteddata->completiongradeitemnumber = null;
$expecteddata->showdescription = $assigncm->showdescription;
+ $expecteddata->downloadcontent = $assigncm->downloadcontent;
$expecteddata->tags = core_tag_tag::get_item_tags_array('core', 'course_modules', $assigncm->id);
$expecteddata->availabilityconditionsjson = null;
$expecteddata->advancedgradingmethod_submissions = null;
diff --git a/lang/en/course.php b/lang/en/course.php
index 5d5861e095f..a5a83064a8a 100644
--- a/lang/en/course.php
+++ b/lang/en/course.php
@@ -79,6 +79,8 @@ $string['customfieldsettings'] = 'Common course custom fields settings';
$string['downloadcourseconfirmation'] = 'You are about to download a zip file of course content (excluding items which cannot be downloaded and any files larger than {$a}).';
$string['downloadcoursecontent'] = 'Download course content';
$string['downloadcoursecontent_help'] = 'This setting determines whether course content may be downloaded by users with the download course content capability (by default users with the role of student or teacher).';
+$string['downloadcontent'] = 'Include in course downloads (if that feature is enabled)';
+$string['downloadcontent_help'] = 'This setting determines whether this activity can be downloaded when download course content is enabled for this course';
$string['enabledownloadcoursecontent'] = 'Enable download course content';
$string['errorendbeforestart'] = 'The end date ({$a}) is before the course start date.';
$string['favourite'] = 'Starred course';
diff --git a/lib/classes/content.php b/lib/classes/content.php
index d9ad2c91599..20bf4a156d9 100644
--- a/lib/classes/content.php
+++ b/lib/classes/content.php
@@ -71,6 +71,13 @@ class content {
}
} else if ($currentcontext->contextlevel == CONTEXT_MODULE) {
+ $cm = get_fast_modinfo($currentcontext->get_course_context()->instanceid)->cms[$currentcontext->instanceid];
+
+ // Do not export course content if disabled at activity level.
+ if (isset($cm->downloadcontent) && $cm->downloadcontent == DOWNLOAD_COURSE_CONTENT_DISABLED) {
+ return false;
+ }
+
// Modules can only be exported if exporting is allowed in their course context.
$canexport = self::can_export_context($currentcontext->get_course_context(), $user);
}
diff --git a/lib/db/install.xml b/lib/db/install.xml
index d59520dde26..5f0e91518ee 100644
--- a/lib/db/install.xml
+++ b/lib/db/install.xml
@@ -308,6 +308,7 @@
+
diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php
index 4f79b7bbd54..d9abd50a0bf 100644
--- a/lib/db/upgrade.php
+++ b/lib/db/upgrade.php
@@ -3116,5 +3116,19 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2021110100.00);
}
+ if ($oldversion < 2021110800.02) {
+ // Define a field 'downloadcontent' in the 'course_modules' table.
+ $table = new xmldb_table('course_modules');
+ $field = new xmldb_field('downloadcontent', XMLDB_TYPE_INTEGER, '1', null, null, null, 1, 'deletioninprogress');
+
+ // Conditionally launch add field 'downloadcontent'.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2021110800.02);
+ }
+
return true;
}
diff --git a/lib/modinfolib.php b/lib/modinfolib.php
index 191e5e062a6..b962a133385 100644
--- a/lib/modinfolib.php
+++ b/lib/modinfolib.php
@@ -843,6 +843,7 @@ class course_modinfo {
* @property-read string $afterlink Extra HTML code to display after link - calculated on request
* @property-read string $afterediticons Extra HTML code to display after editing icons (e.g. more icons) - calculated on request
* @property-read bool $deletioninprogress True if this course module is scheduled for deletion, false otherwise.
+ * @property-read bool $downloadcontent True if content download is enabled for this course module, false otherwise.
*/
class cm_info implements IteratorAggregate {
/**
@@ -1156,6 +1157,11 @@ class cm_info implements IteratorAggregate {
*/
private $deletioninprogress;
+ /**
+ * @var int enable/disable download content for this course module
+ */
+ private $downloadcontent;
+
/**
* List of class read-only properties and their getter methods.
* Used by magic functions __get(), __isset(), __empty()
@@ -1209,7 +1215,8 @@ class cm_info implements IteratorAggregate {
'visible' => false,
'visibleoncoursepage' => false,
'visibleold' => false,
- 'deletioninprogress' => false
+ 'deletioninprogress' => false,
+ 'downloadcontent' => false
);
/**
@@ -1656,7 +1663,8 @@ class cm_info implements IteratorAggregate {
static $cmfields = array('id', 'course', 'module', 'instance', 'section', 'idnumber', 'added',
'score', 'indent', 'visible', 'visibleoncoursepage', 'visibleold', 'groupmode', 'groupingid',
'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected', 'completionpassgrade',
- 'showdescription', 'availability', 'deletioninprogress');
+ 'showdescription', 'availability', 'deletioninprogress', 'downloadcontent');
+
foreach ($cmfields as $key) {
$cmrecord->$key = $this->$key;
}
@@ -1869,6 +1877,7 @@ class cm_info implements IteratorAggregate {
$this->score = isset($mod->score) ? $mod->score : 0;
$this->visibleold = isset($mod->visibleold) ? $mod->visibleold : 0;
$this->deletioninprogress = isset($mod->deletioninprogress) ? $mod->deletioninprogress : 0;
+ $this->downloadcontent = $mod->downloadcontent ?? null;
// Note: it saves effort and database space to always include the
// availability and completion fields, even if availability or completion
diff --git a/version.php b/version.php
index 0f8193e3f90..fd8a130bf19 100644
--- a/version.php
+++ b/version.php
@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
-$version = 2021110800.01; // YYYYMMDD = weekly release date of this DEV branch.
+$version = 2021110800.02; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '4.0dev+ (Build: 20211106)'; // Human-friendly version name