Merge branch 'MDL-69559-master-7' of git://github.com/mickhawkins/moodle
This commit is contained in:
@@ -22,6 +22,8 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use core_admin\local\settings\filesize;
|
||||
|
||||
$capabilities = array(
|
||||
'moodle/backup:backupcourse',
|
||||
'moodle/category:manage',
|
||||
@@ -75,6 +77,17 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
||||
$temp->add(new admin_setting_configselect('moodlecourse/visible', new lang_string('visible'), new lang_string('visible_help'),
|
||||
1, $choices));
|
||||
|
||||
// Enable/disable download course content.
|
||||
$choices = [
|
||||
DOWNLOAD_COURSE_CONTENT_DISABLED => new lang_string('no'),
|
||||
DOWNLOAD_COURSE_CONTENT_ENABLED => new lang_string('yes'),
|
||||
];
|
||||
$downloadcontentsitedefault = new admin_setting_configselect('moodlecourse/downloadcontentsitedefault',
|
||||
new lang_string('enabledownloadcoursecontent', 'course'),
|
||||
new lang_string('downloadcoursecontent_help', 'course'), 0, $choices);
|
||||
$downloadcontentsitedefault->add_dependent_on('downloadcoursecontentallowed');
|
||||
$temp->add($downloadcontentsitedefault);
|
||||
|
||||
// Course format.
|
||||
$temp->add(new admin_setting_heading('courseformathdr', new lang_string('type_format', 'plugin'), ''));
|
||||
|
||||
@@ -161,6 +174,21 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
||||
|
||||
$ADMIN->add('courses', $temp);
|
||||
|
||||
// Download course content.
|
||||
$downloadcoursedefaulturl = new moodle_url('/admin/settings.php', ['section' => 'coursesettings']);
|
||||
$temp = new admin_settingpage('downloadcoursecontent', new lang_string('downloadcoursecontent', 'course'));
|
||||
$temp->add(new admin_setting_configcheckbox('downloadcoursecontentallowed',
|
||||
new lang_string('downloadcoursecontentallowed', 'admin'),
|
||||
new lang_string('downloadcoursecontentallowed_desc', 'admin', $downloadcoursedefaulturl->out()), 0));
|
||||
|
||||
// 50MB default maximum size per file when downloading course content.
|
||||
$defaultmaxdownloadsize = 50 * filesize::UNIT_MB;
|
||||
$temp->add(new filesize('maxsizeperdownloadcoursefile', new lang_string('maxsizeperdownloadcoursefile', 'admin'),
|
||||
new lang_string('maxsizeperdownloadcoursefile_desc', 'admin'), $defaultmaxdownloadsize, filesize::UNIT_MB));
|
||||
$temp->hide_if('maxsizeperdownloadcoursefile', 'downloadcoursecontentallowed');
|
||||
|
||||
$ADMIN->add('courses', $temp);
|
||||
|
||||
// "courserequests" settingpage.
|
||||
$temp = new admin_settingpage('courserequest', new lang_string('courserequest'));
|
||||
$temp->add(new admin_setting_configcheckbox('enablecourserequests',
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_course/downloadcontent",["exports","core/config","core/custom_interaction_events","core/modal_factory","jquery","core/pending"],function(a,b,c,d,e,f){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=i(b);c=i(c);d=h(d);e=i(e);f=i(f);function g(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;g=function(){return a};return a}function h(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=g();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function i(a){return a&&a.__esModule?a:{default:a}}var j=function(){var a=new f.default;document.addEventListener("click",function(a){var b=a.target.closest("[data-downloadcourse]");if(b){a.preventDefault();k(b)}});a.resolve()};a.init=j;var k=function(a){d.create({title:a.dataset.downloadTitle,type:d.types.SAVE_CANCEL,body:"<p>".concat(a.dataset.downloadBody,"</p>"),buttons:{save:a.dataset.downloadButtonText},templateContext:{classes:"downloadcoursecontentmodal"}}).then(function(b){b.show();var d=document.querySelector(".modal .downloadcoursecontentmodal [data-action=\"save\"]"),f=document.querySelector(".modal .downloadcoursecontentmodal [data-action=\"cancel\"]"),g=document.querySelector(".modal[data-region=\"modal-container\"]");(0,e.default)(d).on(c.default.events.activate,function(c){return l(c,a,b)});(0,e.default)(f).on(c.default.events.activate,function(){b.destroy()});if(g.querySelector(".downloadcoursecontentmodal")){(0,e.default)(g).on(c.default.events.activate,function(){b.destroy()})}})},l=function(a,c,d){a.preventDefault();var e=document.createElement("form");e.action=c.dataset.downloadLink;e.method="POST";e.target="_blank";var f=document.createElement("input");f.name="sesskey";f.value=b.default.sesskey;e.appendChild(f);e.style.display="none";document.body.appendChild(e);e.submit();document.body.removeChild(e);d.destroy()}});
|
||||
//# sourceMappingURL=downloadcontent.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,125 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Functions related to downloading course content.
|
||||
*
|
||||
* @module core_course/downloadcontent
|
||||
* @package core_course
|
||||
* @copyright 2020 Michael Hawkins <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import Config from 'core/config';
|
||||
import CustomEvents from 'core/custom_interaction_events';
|
||||
import * as ModalFactory from 'core/modal_factory';
|
||||
import jQuery from 'jquery';
|
||||
import Pending from 'core/pending';
|
||||
|
||||
/**
|
||||
* Set up listener to trigger the download course content modal.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
export const init = () => {
|
||||
const pendingPromise = new Pending();
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const downloadModalTrigger = e.target.closest('[data-downloadcourse]');
|
||||
|
||||
if (downloadModalTrigger) {
|
||||
e.preventDefault();
|
||||
displayDownloadConfirmation(downloadModalTrigger);
|
||||
}
|
||||
});
|
||||
|
||||
pendingPromise.resolve();
|
||||
};
|
||||
|
||||
/**
|
||||
* Display the download course content modal.
|
||||
*
|
||||
* @method displayDownloadConfirmation
|
||||
* @param {Object} downloadModalTrigger The DOM element that triggered the download modal.
|
||||
* @return {void}
|
||||
*/
|
||||
const displayDownloadConfirmation = (downloadModalTrigger) => {
|
||||
ModalFactory.create({
|
||||
title: downloadModalTrigger.dataset.downloadTitle,
|
||||
type: ModalFactory.types.SAVE_CANCEL,
|
||||
body: `<p>${downloadModalTrigger.dataset.downloadBody}</p>`,
|
||||
buttons: {
|
||||
save: downloadModalTrigger.dataset.downloadButtonText
|
||||
},
|
||||
templateContext: {
|
||||
classes: 'downloadcoursecontentmodal'
|
||||
}
|
||||
})
|
||||
.then(modal => {
|
||||
// Display the modal.
|
||||
modal.show();
|
||||
|
||||
const saveButton = document.querySelector('.modal .downloadcoursecontentmodal [data-action="save"]');
|
||||
const cancelButton = document.querySelector('.modal .downloadcoursecontentmodal [data-action="cancel"]');
|
||||
const modalContainer = document.querySelector('.modal[data-region="modal-container"]');
|
||||
|
||||
// Create listener to trigger the download when the "Download" button is pressed.
|
||||
jQuery(saveButton).on(CustomEvents.events.activate, (e) => downloadContent(e, downloadModalTrigger, modal));
|
||||
|
||||
// Create listener to destroy the modal when closing modal by cancelling.
|
||||
jQuery(cancelButton).on(CustomEvents.events.activate, () => {
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
// Create listener to destroy the modal when closing modal by clicking outside of it.
|
||||
if (modalContainer.querySelector('.downloadcoursecontentmodal')) {
|
||||
jQuery(modalContainer).on(CustomEvents.events.activate, () => {
|
||||
modal.destroy();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Trigger downloading of course content.
|
||||
*
|
||||
* @method downloadContent
|
||||
* @param {Event} e The event triggering the download.
|
||||
* @param {Object} downloadModalTrigger The DOM element that triggered the download modal.
|
||||
* @param {Object} modal The modal object.
|
||||
* @return {void}
|
||||
*/
|
||||
const downloadContent = (e, downloadModalTrigger, modal) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Create a form to submit the file download request, so we can avoid sending sesskey over GET.
|
||||
const downloadForm = document.createElement('form');
|
||||
downloadForm.action = downloadModalTrigger.dataset.downloadLink;
|
||||
downloadForm.method = 'POST';
|
||||
// Open download in a new tab, so current course view is not disrupted.
|
||||
downloadForm.target = '_blank';
|
||||
const downloadSesskey = document.createElement('input');
|
||||
downloadSesskey.name = 'sesskey';
|
||||
downloadSesskey.value = Config.sesskey;
|
||||
downloadForm.appendChild(downloadSesskey);
|
||||
downloadForm.style.display = 'none';
|
||||
|
||||
document.body.appendChild(downloadForm);
|
||||
downloadForm.submit();
|
||||
document.body.removeChild(downloadForm);
|
||||
|
||||
// Destroy the modal to prevent duplicates if reopened later.
|
||||
modal.destroy();
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Prepares content for buttons/links to course content export/download.
|
||||
*
|
||||
* @package core_course
|
||||
* @copyright 2020 Michael Hawkins <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_course\output;
|
||||
|
||||
/**
|
||||
* Prepares content for buttons/links to course content export/download.
|
||||
*
|
||||
* @package core_course
|
||||
* @copyright 2020 Michael Hawkins <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class content_export_link {
|
||||
|
||||
/**
|
||||
* Prepare and return the various attributes required for a link/button to populate/trigger the download course content modal.
|
||||
*
|
||||
* @param \context $context The context of the content being exported.
|
||||
* @return stdClass
|
||||
*/
|
||||
public static function get_attributes(\context $context): \stdClass {
|
||||
global $CFG;
|
||||
$downloadattr = new \stdClass();
|
||||
$downloadattr->url = new \moodle_url('/course/downloadcontent.php', ['contextid' => $context->id]);
|
||||
$downloadattr->displaystring = get_string('downloadcoursecontent', 'course');
|
||||
$maxfilesize = display_size($CFG->maxsizeperdownloadcoursefile);
|
||||
$downloadlink = new \moodle_url('/course/downloadcontent.php', ['contextid' => $context->id, 'download' => 1]);
|
||||
|
||||
$downloadattr->elementattributes = [
|
||||
'data-downloadcourse' => 1,
|
||||
'data-download-body' => get_string('downloadcourseconfirmation', 'course', $maxfilesize),
|
||||
'data-download-button-text' => get_string('download'),
|
||||
'data-download-link' => $downloadlink->out(false),
|
||||
'data-download-title' => get_string('downloadcoursecontent', 'course'),
|
||||
];
|
||||
|
||||
return $downloadattr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Download course content confirmation and execution.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage course
|
||||
* @copyright 2020 Michael Hawkins <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once('../config.php');
|
||||
|
||||
use core\content;
|
||||
use core\content\export\zipwriter;
|
||||
|
||||
$contextid = required_param('contextid', PARAM_INT);
|
||||
$isdownload = optional_param('download', 0, PARAM_BOOL);
|
||||
$coursecontext = context::instance_by_id($contextid);
|
||||
$courseid = $coursecontext->instanceid;
|
||||
$courselink = new moodle_url('/course/view.php', ['id' => $courseid]);
|
||||
|
||||
if (!\core\content::can_export_context($coursecontext, $USER)) {
|
||||
redirect($courselink);
|
||||
}
|
||||
|
||||
$PAGE->set_url('/course/downloadcontent.php', ['contextid' => $contextid]);
|
||||
require_login($courseid);
|
||||
|
||||
$courseinfo = get_fast_modinfo($courseid)->get_course();
|
||||
$filename = str_replace('/', '', str_replace(' ', '_', $courseinfo->shortname)) . '_' . time() . '.zip';
|
||||
|
||||
// If download confirmed, prepare and start the zipstream of the course download content.
|
||||
if ($isdownload) {
|
||||
confirm_sesskey();
|
||||
|
||||
$exportoptions = null;
|
||||
|
||||
if (!empty($CFG->maxsizeperdownloadcoursefile)) {
|
||||
$exportoptions = new stdClass();
|
||||
$exportoptions->maxfilesize = $CFG->maxsizeperdownloadcoursefile;
|
||||
}
|
||||
|
||||
// Use file writer in debug developer mode, so any errors can be displayed instead of being streamed into the output file.
|
||||
if (debugging('', DEBUG_DEVELOPER)) {
|
||||
$writer = zipwriter::get_file_writer($filename, $exportoptions);
|
||||
|
||||
ob_start();
|
||||
content::export_context($coursecontext, $USER, $writer);
|
||||
$content = ob_get_clean();
|
||||
|
||||
// If no errors found, output the file.
|
||||
if (empty($content)) {
|
||||
send_file($writer->get_file_path(), $filename);
|
||||
redirect($courselink);
|
||||
} else {
|
||||
// If any errors occurred, display them instead of outputting the file.
|
||||
debugging("Errors found while producing the download course content output:\n {$content}", DEBUG_DEVELOPER);
|
||||
}
|
||||
} else {
|
||||
// If not developer debugging, stream the output file directly.
|
||||
$writer = zipwriter::get_stream_writer($filename, $exportoptions);
|
||||
content::export_context($coursecontext, $USER, $writer);
|
||||
|
||||
redirect($courselink);
|
||||
}
|
||||
|
||||
} else {
|
||||
$PAGE->set_title(get_string('downloadcoursecontent', 'course'));
|
||||
$PAGE->set_heading(format_string($courseinfo->fullname));
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(get_string('downloadcoursecontent', 'course'));
|
||||
|
||||
// Prepare download confirmation information and display it.
|
||||
$maxfilesize = display_size($CFG->maxsizeperdownloadcoursefile);
|
||||
$downloadlink = new moodle_url('/course/downloadcontent.php', ['contextid' => $contextid, 'download' => 1]);
|
||||
|
||||
echo $OUTPUT->confirm(get_string('downloadcourseconfirmation', 'course', $maxfilesize), $downloadlink, $courselink);
|
||||
}
|
||||
@@ -120,6 +120,27 @@ class course_edit_form extends moodleform {
|
||||
$mform->setConstant('visible', $courseconfig->visible);
|
||||
}
|
||||
}
|
||||
|
||||
// Download course content.
|
||||
if ($CFG->downloadcoursecontentallowed) {
|
||||
$downloadchoices = [
|
||||
DOWNLOAD_COURSE_CONTENT_DISABLED => get_string('no'),
|
||||
DOWNLOAD_COURSE_CONTENT_ENABLED => get_string('yes'),
|
||||
];
|
||||
$sitedefaultstring = $downloadchoices[$courseconfig->downloadcontentsitedefault];
|
||||
$downloadchoices[DOWNLOAD_COURSE_CONTENT_SITE_DEFAULT] = get_string('sitedefaultspecified', '', $sitedefaultstring);
|
||||
$downloadselectdefault = $courseconfig->downloadcontent ?? DOWNLOAD_COURSE_CONTENT_SITE_DEFAULT;
|
||||
|
||||
$mform->addElement('select', 'downloadcontent', get_string('enabledownloadcoursecontent', 'course'), $downloadchoices);
|
||||
$mform->addHelpButton('downloadcontent', 'downloadcoursecontent', 'course');
|
||||
$mform->setDefault('downloadcontent', $downloadselectdefault);
|
||||
|
||||
if (!has_capability('moodle/course:configuredownloadcontent', $coursecontext)) {
|
||||
$mform->hardFreeze('downloadcontent');
|
||||
$mform->setConstant('downloadcontent', $downloadselectdefault);
|
||||
}
|
||||
}
|
||||
|
||||
$mform->addElement('date_time_selector', 'startdate', get_string('startdate'));
|
||||
$mform->addHelpButton('startdate', 'startdate');
|
||||
$date = (new DateTime())->setTimestamp(usergetmidnight(time()));
|
||||
|
||||
@@ -71,6 +71,11 @@ define('COURSE_CUSTOMFIELD_EMPTY', -1);
|
||||
// Course activity chooser footer default display option.
|
||||
define('COURSE_CHOOSER_FOOTER_NONE', 'hidden');
|
||||
|
||||
// Download course content options.
|
||||
define('DOWNLOAD_COURSE_CONTENT_DISABLED', 0);
|
||||
define('DOWNLOAD_COURSE_CONTENT_ENABLED', 1);
|
||||
define('DOWNLOAD_COURSE_CONTENT_SITE_DEFAULT', 2);
|
||||
|
||||
function make_log_url($module, $url) {
|
||||
switch ($module) {
|
||||
case 'course':
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
@core @core_course
|
||||
Feature: Course content can be downloaded
|
||||
In order to retain a backup offline copy of course activity/resource data
|
||||
As a user
|
||||
I can download a course's content
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Hockey 101 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
And I log in as "admin"
|
||||
And I navigate to "Courses > Courses > Download course content" in site administration
|
||||
And I set the following fields to these values:
|
||||
| Download course content feature available | 1 |
|
||||
And I press "Save changes"
|
||||
And I navigate to "Courses > Courses > Course default settings" in site administration
|
||||
And I set the field "Enable download course content" to "Yes"
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
|
||||
@javascript
|
||||
Scenario: A student can download course content when the feature is enabled in their course
|
||||
Given I log in as "student1"
|
||||
When I am on "Hockey 101" course homepage
|
||||
And "Download course content" "button" should exist
|
||||
And I press "Download course content"
|
||||
Then I should see "You are about to download a zip file"
|
||||
# Without the ability to check the downloaded file, the absence of an exception being thrown here is considered a success.
|
||||
And I click on "Download" "button" in the "Download course content" "dialogue"
|
||||
|
||||
@javascript
|
||||
Scenario: A teacher can download course content when the feature is enabled in their course
|
||||
Given I log in as "teacher1"
|
||||
When I am on "Hockey 101" course homepage
|
||||
And "Download course content" "link" should exist in current page administration
|
||||
And I navigate to "Download course content" in current page administration
|
||||
Then I should see "You are about to download a zip file"
|
||||
# Without the ability to check the downloaded file, the absence of an exception being thrown here is considered a success.
|
||||
And I click on "Download" "button" in the "Download course content" "dialogue"
|
||||
@@ -0,0 +1,112 @@
|
||||
@core @core_course
|
||||
Feature: Access to downloading course content can be controlled
|
||||
In order to allow or restrict access to download course content
|
||||
As a trusted user
|
||||
I can control access to the download course content feature
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Hockey 101 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
And I log in as "admin"
|
||||
And the following config values are set as admin:
|
||||
| downloadcoursecontentallowed | 1 |
|
||||
And I log out
|
||||
|
||||
Scenario: Site admins can remove the download course content feature
|
||||
Given I log in as "admin"
|
||||
And I am on "Hockey 101" course homepage
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And I set the field "Enable download course content" to "Yes"
|
||||
And I press "Save and display"
|
||||
And "Download course content" "link" should exist in current page administration
|
||||
When the following config values are set as admin:
|
||||
| downloadcoursecontentallowed | 0 |
|
||||
And I am on "Hockey 101" course homepage
|
||||
Then "Download course content" "link" should not exist in current page administration
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And I should not see "Enable download course content"
|
||||
|
||||
Scenario: Site admins can set the default value for whether download course content is enabled in courses
|
||||
Given I log in as "admin"
|
||||
And I am on "Hockey 101" course homepage
|
||||
And "Download course content" "link" should not exist in current page administration
|
||||
When I navigate to "Courses > Courses > Course default settings" in site administration
|
||||
And I set the field "Enable download course content" to "Yes"
|
||||
And I press "Save changes"
|
||||
And I am on "Hockey 101" course homepage
|
||||
Then "Download course content" "link" should exist in current page administration
|
||||
|
||||
Scenario: A teacher can enable and disable the download course content feature when it is available
|
||||
Given I log in as "teacher1"
|
||||
When I am on "Hockey 101" course homepage
|
||||
And "Download course content" "link" should not exist in current page administration
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And I should see "Enable download course content"
|
||||
And I set the field "Enable download course content" to "Yes"
|
||||
And I press "Save and display"
|
||||
Then "Download course content" "link" should exist in current page administration
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And I set the field "Enable download course content" to "No"
|
||||
And I press "Save and display"
|
||||
And "Download course content" "link" should not exist in current page administration
|
||||
|
||||
Scenario: Teachers require a capability to access the download course content feature or modify its availability in a course
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Courses > Courses > Course default settings" in site administration
|
||||
And I set the field "Enable download course content" to "Yes"
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
# Check teacher can see download option and enable dropdown.
|
||||
And I log in as "teacher1"
|
||||
And I am on "Hockey 101" course homepage
|
||||
And "Download course content" "link" should exist in current page administration
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And "Enable download course content" "select" should exist
|
||||
And I log out
|
||||
# Remove teacher's capabilities for download course content.
|
||||
And I log in as "admin"
|
||||
And I set the following system permissions of "Teacher" role:
|
||||
| capability | permission |
|
||||
| moodle/course:downloadcoursecontent | Prohibit |
|
||||
| moodle/course:configuredownloadcontent | Prohibit |
|
||||
And I log out
|
||||
# Check teacher can no longer see download option, and that enable value is visible, but dropdown no longer available.
|
||||
When I log in as "teacher1"
|
||||
And I am on "Hockey 101" course homepage
|
||||
Then "Download course content" "link" should not exist in current page administration
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And I should see "Enable download course content"
|
||||
And I should see "Site default (Yes)"
|
||||
And "Enable download course content" "select" should not exist
|
||||
|
||||
Scenario: Students require a capability to access the download course content feature in a course
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Hockey 101" course homepage
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And I set the field "Enable download course content" to "Yes"
|
||||
And I press "Save and display"
|
||||
And I log out
|
||||
# Check student can see download button.
|
||||
And I log in as "student1"
|
||||
And I am on "Hockey 101" course homepage
|
||||
And "Download course content" "button" should exist
|
||||
And I log out
|
||||
And I log in as "admin"
|
||||
# Remove student's capability for download course content.
|
||||
When I set the following system permissions of "Student" role:
|
||||
| capability | permission |
|
||||
| moodle/course:downloadcoursecontent | Prohibit |
|
||||
And I log out
|
||||
# Check student can no longer see download button.
|
||||
And I log in as "student1"
|
||||
And I am on "Hockey 101" course homepage
|
||||
Then "Download course content" "link" should not exist in current page administration
|
||||
@@ -223,12 +223,21 @@
|
||||
$PAGE->requires->js_init_call('M.core_completion.init');
|
||||
}
|
||||
|
||||
// Determine whether the user has permission to download course content.
|
||||
$candownloadcourse = \core\content::can_export_context($context, $USER);
|
||||
|
||||
// We are currently keeping the button here from 1.x to help new teachers figure out
|
||||
// what to do, even though the link also appears in the course admin block. It also
|
||||
// means you can back out of a situation where you removed the admin block. :)
|
||||
if ($PAGE->user_allowed_editing()) {
|
||||
$buttons = $OUTPUT->edit_button($PAGE->url);
|
||||
$PAGE->set_button($buttons);
|
||||
} else if ($candownloadcourse) {
|
||||
// Show the download course content button if user has permission to access it.
|
||||
// Only showing this if user doesn't have edit rights, since those who do will access it via the actions menu.
|
||||
$buttonattr = \core_course\output\content_export_link::get_attributes($context);
|
||||
$button = new single_button($buttonattr->url, $buttonattr->displaystring, 'post', false, $buttonattr->elementattributes);
|
||||
$PAGE->set_button($OUTPUT->render($button));
|
||||
}
|
||||
|
||||
// If viewing a section, make the title more specific
|
||||
@@ -300,4 +309,9 @@
|
||||
// Include course AJAX
|
||||
include_course_ajax($course, $modnamesused);
|
||||
|
||||
// If available, include the JS to prepare the download course content modal.
|
||||
if ($candownloadcourse) {
|
||||
$PAGE->requires->js_call_amd('core_course/downloadcontent', 'init');
|
||||
}
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
@@ -516,6 +516,8 @@ $string['doctonewwindow'] = 'Open in new window';
|
||||
$string['doesnotfit'] = 'Email display settings';
|
||||
$string['doesnotfitdetail'] = 'Display settings for email leaving Moodle.';
|
||||
$string['download'] = 'Download';
|
||||
$string['downloadcoursecontentallowed'] = 'Download course content feature available';
|
||||
$string['downloadcoursecontentallowed_desc'] = 'Whether the download course content feature is available to courses. When available, course content downloads can be enabled/disabled using the "Enable download course content" setting within the course edit menu (the default for this can be set in <a href={$a} target="_blank">Course default settings</a>).';
|
||||
$string['durationunits'] = 'duration units';
|
||||
$string['edithelpdocs'] = 'Edit help documents';
|
||||
$string['editlang'] = '<b>Edit</b>';
|
||||
@@ -802,6 +804,8 @@ sites. If this is not what you wanted then you should make sure you are updating
|
||||
from a STABLE branch of the Moodle code. See Moodle Docs for more details.';
|
||||
$string['maxbytes'] = 'Maximum uploaded file size';
|
||||
$string['maxconsecutiveidentchars'] = 'Consecutive identical characters';
|
||||
$string['maxsizeperdownloadcoursefile'] = 'Maximum size per file';
|
||||
$string['maxsizeperdownloadcoursefile_desc'] = 'The maximum size of each file when downloading course content. Files exceeding this size will be omitted from the download.';
|
||||
$string['maxeditingtime'] = 'Maximum time to edit posts';
|
||||
$string['maxusersperpage'] = ' Maximum users per page';
|
||||
$string['configmaxusersperpage'] = 'Maximum number of users displayed within user selector in course, group, cohort, webservice etc.';
|
||||
|
||||
@@ -54,6 +54,10 @@ $string['customfield_visibility_help'] = 'This setting determines who can view t
|
||||
$string['customfield_visibletoall'] = 'Everyone';
|
||||
$string['customfield_visibletoteachers'] = 'Teachers';
|
||||
$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['enabledownloadcoursecontent'] = 'Enable download course content';
|
||||
$string['errorendbeforestart'] = 'The end date ({$a}) is before the course start date.';
|
||||
$string['favourite'] = 'Starred course';
|
||||
$string['gradetopassnotset'] = 'This course does not have a grade to pass set. It may be set in the grade item of the course (Gradebook setup).';
|
||||
|
||||
@@ -1930,6 +1930,7 @@ $string['since'] = 'Since';
|
||||
$string['sincelast'] = 'since last login';
|
||||
$string['site'] = 'Site';
|
||||
$string['sitedefault'] = 'Site default';
|
||||
$string['sitedefaultspecified'] = 'Site default ({$a})';
|
||||
$string['siteerrors'] = 'Site errors';
|
||||
$string['sitefiles'] = 'Site files';
|
||||
$string['sitefilesused'] = 'Site files used in this course';
|
||||
|
||||
@@ -172,6 +172,8 @@ $string['course:changelockedcustomfields'] = 'Change locked custom fields';
|
||||
$string['course:changeshortname'] = 'Change course short name';
|
||||
$string['course:changesummary'] = 'Change course summary';
|
||||
$string['course:configurecustomfields'] = 'Configure custom fields';
|
||||
$string['course:configuredownloadcontent'] = 'Configure download course content';
|
||||
$string['course:downloadcoursecontent'] = 'Download course content';
|
||||
$string['course:enrolconfig'] = 'Configure enrol instances in courses';
|
||||
$string['course:enrolreview'] = 'Review course enrolments';
|
||||
$string['course:setforcedlanguage'] = 'Force course language';
|
||||
|
||||
+24
-1
@@ -52,7 +52,30 @@ class content {
|
||||
* @return bool
|
||||
*/
|
||||
public static function can_export_context(context $currentcontext, stdClass $user): bool {
|
||||
return true;
|
||||
global $CFG;
|
||||
|
||||
$canexport = false;
|
||||
|
||||
if ($currentcontext->contextlevel == CONTEXT_COURSE) {
|
||||
if ($CFG->downloadcoursecontentallowed &&
|
||||
has_capability('moodle/course:downloadcoursecontent', $currentcontext, $user)) {
|
||||
|
||||
$courseinfo = get_fast_modinfo($currentcontext->instanceid)->get_course();
|
||||
|
||||
// If enabled/disabled explicitly set on course, use that as the course setting, otherwise use site default.
|
||||
if (isset($courseinfo->downloadcontent) && $courseinfo->downloadcontent != DOWNLOAD_COURSE_CONTENT_SITE_DEFAULT) {
|
||||
$canexport = $courseinfo->downloadcontent;
|
||||
} else {
|
||||
$canexport = get_config('moodlecourse')->downloadcontentsitedefault;
|
||||
}
|
||||
|
||||
}
|
||||
} else if ($currentcontext->contextlevel == CONTEXT_MODULE) {
|
||||
// Modules can only be exported if exporting is allowed in their course context.
|
||||
$canexport = self::can_export_context($currentcontext->get_course_context(), $user);
|
||||
}
|
||||
|
||||
return $canexport;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2576,4 +2576,26 @@ $capabilities = array(
|
||||
'editingteacher' => CAP_ALLOW,
|
||||
]
|
||||
],
|
||||
|
||||
// Allow users to download course content.
|
||||
'moodle/course:downloadcoursecontent' => [
|
||||
'captype' => 'read',
|
||||
'contextlevel' => CONTEXT_COURSE,
|
||||
'archetypes' => array(
|
||||
'student' => CAP_ALLOW,
|
||||
'teacher' => CAP_ALLOW,
|
||||
'editingteacher' => CAP_ALLOW,
|
||||
'manager' => CAP_ALLOW
|
||||
)
|
||||
],
|
||||
|
||||
// Allow users to configure download course content functionality within a course, if the feature is available.
|
||||
'moodle/course:configuredownloadcontent' => [
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_COURSE,
|
||||
'archetypes' => array(
|
||||
'editingteacher' => CAP_ALLOW,
|
||||
'manager' => CAP_ALLOW
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="lib/db" VERSION="20201007" COMMENT="XMLDB file for core Moodle tables"
|
||||
<XMLDB PATH="lib/db" VERSION="20201021" COMMENT="XMLDB file for core Moodle tables"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@@ -89,6 +89,7 @@
|
||||
<FIELD NAME="showreports" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="visible" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
|
||||
<FIELD NAME="visibleold" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="the state of visible field when hiding parent category, this helps us to recover hidden states when unhiding the parent category later"/>
|
||||
<FIELD NAME="downloadcontent" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="groupmode" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="groupmodeforce" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="defaultgroupingid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="default grouping used in course modules, does not have key intentionally"/>
|
||||
@@ -4322,4 +4323,4 @@
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
</XMLDB>
|
||||
@@ -2774,6 +2774,7 @@ function xmldb_main_upgrade($oldversion) {
|
||||
|
||||
// Add example field.
|
||||
$field = new xmldb_field('example', XMLDB_TYPE_TEXT, null, null, null, null, null, 'tutorial');
|
||||
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
@@ -2862,5 +2863,18 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2021052500.30);
|
||||
}
|
||||
|
||||
if ($oldversion < 2021052500.32) {
|
||||
// Define field downloadcontent to be added to course.
|
||||
$table = new xmldb_table('course');
|
||||
$field = new xmldb_field('downloadcontent', XMLDB_TYPE_INTEGER, '1', null, null, null, null, 'visibleold');
|
||||
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2021052500.32);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
+11
-1
@@ -4448,7 +4448,7 @@ class settings_navigation extends navigation_node {
|
||||
* @return navigation_node|false
|
||||
*/
|
||||
protected function load_course_settings($forceopen = false) {
|
||||
global $CFG;
|
||||
global $CFG, $USER;
|
||||
require_once($CFG->dirroot . '/course/lib.php');
|
||||
|
||||
$course = $this->page->course;
|
||||
@@ -4605,6 +4605,16 @@ class settings_navigation extends navigation_node {
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare data for course content download functionality if it is enabled.
|
||||
// Will only be included here if the action menu is already in use, otherwise a button will be added to the UI elsewhere.
|
||||
if (\core\content::can_export_context($coursecontext, $USER) && !empty($coursenode->get_children_key_list())) {
|
||||
$linkattr = \core_course\output\content_export_link::get_attributes($coursecontext);
|
||||
$actionlink = new action_link($linkattr->url, $linkattr->displaystring, null, $linkattr->elementattributes);
|
||||
|
||||
$coursenode->add($linkattr->displaystring, $actionlink, self::TYPE_SETTING, null, 'download',
|
||||
new pix_icon('t/download', ''));
|
||||
}
|
||||
|
||||
// Return we are done
|
||||
return $coursenode;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Unit tests for core\content class.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2020 Michael Hawkins <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core;
|
||||
|
||||
/**
|
||||
* Unit tests for core\content class.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2020 Michael Hawkins <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class content_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* A test to confirm only valid cases allow exporting of course content.
|
||||
*/
|
||||
public function test_can_export_context_course() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course1 = $this->getDataGenerator()->create_course();
|
||||
$course2 = $this->getDataGenerator()->create_course();
|
||||
$course1context = \context_course::instance($course1->id);
|
||||
$course2context = \context_course::instance($course2->id);
|
||||
|
||||
// Enrol user as student in course1 only.
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course1, 'student');
|
||||
|
||||
// Confirm by default enrolled user does not have permission to export in course1.
|
||||
$this->assertFalse(content::can_export_context($course1context, $user));
|
||||
|
||||
// Make course download available on site, but not enabled in course1 or by default.
|
||||
set_config('downloadcoursecontentallowed', true);
|
||||
|
||||
// Confirm user still does not have permission to export (disabled in courses by default).
|
||||
$this->assertFalse(content::can_export_context($course1context, $user));
|
||||
|
||||
// Enable export in courses by default.
|
||||
set_config('downloadcontentsitedefault', DOWNLOAD_COURSE_CONTENT_ENABLED, 'moodlecourse');
|
||||
|
||||
// Confirm user now has permission to export in course1 only.
|
||||
$this->assertTrue(content::can_export_context($course1context, $user));
|
||||
|
||||
// Disable course downloads in course1.
|
||||
$course1->downloadcontent = DOWNLOAD_COURSE_CONTENT_DISABLED;
|
||||
$DB->update_record('course', $course1);
|
||||
rebuild_course_cache($course1->id);
|
||||
|
||||
// Confirm user does not have permission to export in course1.
|
||||
$this->assertFalse(content::can_export_context($course1context, $user));
|
||||
|
||||
// Enable course downloads in course1.
|
||||
$course1->downloadcontent = DOWNLOAD_COURSE_CONTENT_ENABLED;
|
||||
$DB->update_record('course', $course1);
|
||||
rebuild_course_cache($course1->id);
|
||||
|
||||
// Confirm user has permission to export in course1.
|
||||
$this->assertTrue(content::can_export_context($course1context, $user));
|
||||
|
||||
// Confirm user does not have permission to export in course they are not enrolled in (course2).
|
||||
$this->assertFalse(content::can_export_context($course2context, $user));
|
||||
|
||||
// Disable export in courses by default.
|
||||
set_config('downloadcontentsitedefault', DOWNLOAD_COURSE_CONTENT_DISABLED, 'moodlecourse');
|
||||
|
||||
// Confirm user still has permission to export in course1 (still enabled at the course level).
|
||||
$this->assertTrue(content::can_export_context($course1context, $user));
|
||||
|
||||
// Disable the course downloads feature.
|
||||
set_config('downloadcoursecontentallowed', false);
|
||||
|
||||
// Confirm user no longer has permission to export in course1.
|
||||
$this->assertFalse(content::can_export_context($course1context, $user));
|
||||
}
|
||||
|
||||
/**
|
||||
* A test to confirm unsupported contexts will return false when checking whether content can be exported.
|
||||
*/
|
||||
public function test_can_export_context_unsupported_context() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course1 = $this->getDataGenerator()->create_course();
|
||||
$systemcontext = \context_system::instance();
|
||||
|
||||
// Enrol user as student in course1 only.
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course1, 'student');
|
||||
|
||||
// Make course download available on site (course context).
|
||||
set_config('downloadcoursecontentallowed', true);
|
||||
|
||||
// Confirm system context does not gain permission to export content.
|
||||
$this->assertFalse(content::can_export_context($systemcontext, $user));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2021052500.31; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2021052500.32; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '4.0dev (Build: 20201023)'; // Human-friendly version name
|
||||
|
||||
Reference in New Issue
Block a user