mirror of
https://github.com/moodle/moodle.git
synced 2025-08-04 16:36:37 +02:00
MDL-69559 course: Add course content download UI and execution page
This commit is contained in:
parent
cdf91cc236
commit
c053b93f0c
8 changed files with 308 additions and 1 deletions
2
course/amd/build/downloadcontent.min.js
vendored
Normal file
2
course/amd/build/downloadcontent.min.js
vendored
Normal file
|
@ -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
|
1
course/amd/build/downloadcontent.min.js.map
Normal file
1
course/amd/build/downloadcontent.min.js.map
Normal file
File diff suppressed because one or more lines are too long
125
course/amd/src/downloadcontent.js
Normal file
125
course/amd/src/downloadcontent.js
Normal file
|
@ -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 <michaelh@moodle.com>
|
||||||
|
* @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();
|
||||||
|
};
|
60
course/classes/output/content_export_link.php
Normal file
60
course/classes/output/content_export_link.php
Normal file
|
@ -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 <michaelh@moodle.com>
|
||||||
|
* @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 <michaelh@moodle.com>
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
94
course/downloadcontent.php
Normal file
94
course/downloadcontent.php
Normal file
|
@ -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 <michaelh@moodle.com>
|
||||||
|
* @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);
|
||||||
|
}
|
|
@ -223,12 +223,21 @@
|
||||||
$PAGE->requires->js_init_call('M.core_completion.init');
|
$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
|
// 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
|
// 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. :)
|
// means you can back out of a situation where you removed the admin block. :)
|
||||||
if ($PAGE->user_allowed_editing()) {
|
if ($PAGE->user_allowed_editing()) {
|
||||||
$buttons = $OUTPUT->edit_button($PAGE->url);
|
$buttons = $OUTPUT->edit_button($PAGE->url);
|
||||||
$PAGE->set_button($buttons);
|
$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
|
// If viewing a section, make the title more specific
|
||||||
|
@ -300,4 +309,9 @@
|
||||||
// Include course AJAX
|
// Include course AJAX
|
||||||
include_course_ajax($course, $modnamesused);
|
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();
|
echo $OUTPUT->footer();
|
||||||
|
|
|
@ -54,6 +54,7 @@ $string['customfield_visibility_help'] = 'This setting determines who can view t
|
||||||
$string['customfield_visibletoall'] = 'Everyone';
|
$string['customfield_visibletoall'] = 'Everyone';
|
||||||
$string['customfield_visibletoteachers'] = 'Teachers';
|
$string['customfield_visibletoteachers'] = 'Teachers';
|
||||||
$string['customfieldsettings'] = 'Common course custom fields settings';
|
$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'] = '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['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['enabledownloadcoursecontent'] = 'Enable download course content';
|
||||||
|
|
|
@ -4448,7 +4448,7 @@ class settings_navigation extends navigation_node {
|
||||||
* @return navigation_node|false
|
* @return navigation_node|false
|
||||||
*/
|
*/
|
||||||
protected function load_course_settings($forceopen = false) {
|
protected function load_course_settings($forceopen = false) {
|
||||||
global $CFG;
|
global $CFG, $USER;
|
||||||
require_once($CFG->dirroot . '/course/lib.php');
|
require_once($CFG->dirroot . '/course/lib.php');
|
||||||
|
|
||||||
$course = $this->page->course;
|
$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 we are done
|
||||||
return $coursenode;
|
return $coursenode;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue