Merge branch 'MDL-66679-master' of https://github.com/sammarshallou/moodle

This commit is contained in:
Adrian Greeve 2019-11-04 15:45:36 +08:00 committed by Jun Pataleta
commit f025022d62
11 changed files with 162 additions and 3 deletions

View File

@ -45,6 +45,9 @@ class grade_export_xls extends grade_export {
$strgrades = get_string('grades');
// If this file was requested from a form, then mark download as complete (before sending headers).
\core_form\util::form_download_complete();
// Calculate file name
$shortname = format_string($this->course->shortname, true, array('context' => context_course::instance($this->course->id)));
$downloadfilename = clean_filename("$shortname $strgrades.xls");

View File

@ -503,6 +503,9 @@ class csv_export_writer {
* Download the csv file.
*/
public function download_file() {
// If this file was requested from a form, then mark download as complete.
\core_form\util::form_download_complete();
$this->send_header();
$this->print_csv_data();
exit;

View File

@ -54,6 +54,9 @@ function download_as_dataformat($filename, $dataformat, $columns, $iterator, $ca
// Close the session so that the users other tabs in the same session are not blocked.
\core\session\manager::write_close();
// If this file was requested from a form, then mark download as complete (before sending headers).
\core_form\util::form_download_complete();
$format->set_filename($filename);
$format->send_http_headers();
// This exists to support all dataformats - see MDL-56046.

View File

@ -2299,6 +2299,9 @@ function send_temp_file($path, $filename, $pathisstring=false) {
$filename = urlencode($filename);
}
// If this file was requested from a form, then mark download as complete.
\core_form\util::form_download_complete();
header('Content-Disposition: attachment; filename="'.$filename.'"');
if (is_https()) { // HTTPS sites - watch out for IE! KB812935 and KB316431.
header('Cache-Control: private, max-age=10, no-transform');
@ -2450,6 +2453,9 @@ function send_file($path, $filename, $lifetime = null , $filter=0, $pathisstring
if ($forcedownload) {
header('Content-Disposition: attachment; filename="'.$filename.'"');
// If this file was requested from a form, then mark download as complete.
\core_form\util::form_download_complete();
} else if ($mimetype !== 'application/x-shockwave-flash') {
// If this is an swf don't pass content-disposition with filename as this makes the flash player treat the file
// as an upload and enforces security that may prevent the file from being loaded.

View File

@ -1,2 +1,2 @@
define ("core_form/submit",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;a.init=function init(a){var b=document.getElementById(a);b.form.addEventListener("submit",function(){var a=function(){b.disabled=!0};window.addEventListener("beforeunload",a);setTimeout(function(){window.removeEventListener("beforeunload",a)},0)},!1)}});
define ("core_form/submit",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;var b=0,c=[],d=function(a){c.push(a);if(!b){b=setInterval(function(){var a=document.cookie.split(e()+"=");if(2==a.length){f();clearInterval(b);b=0;c.forEach(function(a){a.disabled=!1})}},500)}},e=function(){return"moodledownload_"+M.cfg.sesskey},f=function(){document.cookie=encodeURIComponent(e())+"=deleted; expires="+new Date(0).toUTCString()};a.init=function init(a){var b=document.getElementById(a);if("off"===b.form.dataset.doubleSubmitProtection){return}b.form.addEventListener("submit",function(a){var c=function(){if(a.defaultPrevented||b.disabled){return}b.disabled=!0;f();d(b)};window.addEventListener("beforeunload",c);setTimeout(function(){window.removeEventListener("beforeunload",c)},0)},!1)}});
//# sourceMappingURL=submit.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -24,6 +24,60 @@
* @since 3.8
*/
/** @type {number} ID for setInterval used when polling for download cookie */
let cookieListener = 0;
/** @type {Array} Array of buttons that need re-enabling if we get a download cookie */
const cookieListeningButtons = [];
/**
* Listens in case a download cookie is provided.
*
* This function is used to detect file downloads. If there is a file download then we get a
* beforeunload event, but the page is never unloaded and when the file download completes we
* should re-enable the buttons. We detect this by watching for a specific cookie.
*
* PHP function \core_form\util::form_download_complete() can be used to send this cookie.
*
* @param {HTMLElement} button Button to re-enable
*/
const listenForDownloadCookie = (button) => {
cookieListeningButtons.push(button);
if (!cookieListener) {
cookieListener = setInterval(() => {
// Look for cookie.
const parts = document.cookie.split(getCookieName() + '=');
if (parts.length == 2) {
// We found the cookie, so the file is ready. Expire the cookie and cancel polling.
clearDownloadCookie();
clearInterval(cookieListener);
cookieListener = 0;
// Re-enable all the buttons.
cookieListeningButtons.forEach((button) => {
button.disabled = false;
});
}
}, 500);
}
};
/**
* Gets a unique name for the download cookie.
*
* @returns {string} Cookie name
*/
const getCookieName = () => {
return 'moodledownload_' + M.cfg.sesskey;
};
/**
* Clears the download cookie if there is one.
*/
const clearDownloadCookie = () => {
document.cookie = encodeURIComponent(getCookieName()) + '=deleted; expires=' + new Date(0).toUTCString();
};
/**
* Initialises submit buttons.
*
@ -31,11 +85,22 @@
*/
export const init = (elementId) => {
const button = document.getElementById(elementId);
button.form.addEventListener('submit', function() {
// If the form has double submit protection disabled, do nothing.
if (button.form.dataset.doubleSubmitProtection === 'off') {
return;
}
button.form.addEventListener('submit', function(event) {
// Only disable it if the browser is really going to another page as a result of the
// submit.
const disableAction = function() {
// If the submit was cancelled, or the button is already disabled, don't do anything.
if (event.defaultPrevented || button.disabled) {
return;
}
button.disabled = true;
clearDownloadCookie();
listenForDownloadCookie(button);
};
window.addEventListener('beforeunload', disableAction);
// If there is no beforeunload event as a result of this form submit, then the form

64
lib/form/classes/util.php Normal file
View File

@ -0,0 +1,64 @@
<?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/>.
/**
* Provides the {@link core_form\util} class.
*
* @package core_form
* @copyright 2019 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_form;
defined('MOODLE_INTERNAL') || die();
/**
* General utility class for form-related methods.
*
* @copyright 2019 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class util {
/**
* This function should be called if a form submit results in a file download (i.e. with the
* Content-Disposition: attachment header) instead of navigating to a new page, before the
* file download is sent. It will set a cookie which is used to inform page javascript in
* submit.js.
*
* You may call this function in scripts which might not necessarily be called from forms; it
* will only set the cookie if there is a POST request from a form.
*
* This is automatically called from various points in Moodle such as send_file_xx functions
* in filelib.php.
*/
public static function form_download_complete() {
// If this doesn't look like a Moodle QuickForms request, ignore.
$quickform = false;
foreach ($_POST as $name => $value) {
if (preg_match('~^_qf__~', $name)) {
$quickform = true;
break;
}
}
if (!$quickform) {
return;
}
// Set a session cookie.
setcookie('moodledownload_' . sesskey(), time());
}
}

View File

@ -169,6 +169,10 @@ abstract class moodleform {
* @param mixed $attributes you can pass a string of html attributes here or an array.
* Special attribute 'data-random-ids' will randomise generated elements ids. This
* is necessary when there are several forms on the same page.
* Special attribute 'data-double-submit-protection' set to 'off' will turn off
* double-submit protection JavaScript - this may be necessary if your form sends
* downloadable files in response to a submit button, and can't call
* \core_form\util::form_download_complete();
* @param bool $editable
* @param array $ajaxformdata Forms submitted via ajax, must pass their data here, instead of relying on _GET and _POST.
*/

View File

@ -76,6 +76,13 @@ validation against and defaults to null (so, no user needed) if not provided.
the itemid and filepath for the filearea and path defined in $args. It has been added in order to get the correct itemid and
filepath because some components, such as mod_page or mod_resource, add the revision to the URL where the itemid should be placed
(to prevent caching problems), but then they don't store it in database.
* New utility function \core_form\util::form_download_complete should be called if your code sends
a file with Content-Disposition: Attachment in response to a Moodle form submit button (to ensure
that disabled submit buttons get re-enabled in that case). It is automatically called by the
filelib.php send_xx functions.
* If you have a form which sends a file in response to a Moodle form submit button, but you cannot
call the above function because the file is sent by a third party library, then you should add
the attribute data-double-submit-protection="off" to your form.
=== 3.7 ===

View File

@ -140,6 +140,10 @@ if (optional_param('sesskey', false, PARAM_BOOL) && confirm_sesskey()) {
header('Expires: 0');
header('Cache-Control: must-revalidate,post-check=0,pre-check=0');
header('Pragma: public');
// If this file was requested from a form, then mark download as complete.
\core_form\util::form_download_complete();
$exportfilehandler = fopen($exportfile, 'rb');
print fread($exportfilehandler, filesize($exportfile));
fclose($exportfilehandler);