--- /dev/null
+// 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();
+};
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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);
+}
$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
// 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();
$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';
* @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;
}
}
+ // 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;
}