diff --git a/lib/editor/atto/autosave-ajax.php b/lib/editor/atto/autosave-ajax.php
index 6626075a9fb..a31117c02ea 100644
--- a/lib/editor/atto/autosave-ajax.php
+++ b/lib/editor/atto/autosave-ajax.php
@@ -27,156 +27,195 @@ define('AJAX_SCRIPT', true);
require_once(dirname(__FILE__) . '/../../../config.php');
require_once($CFG->libdir . '/filestorage/file_storage.php');
-$contextid = required_param('contextid', PARAM_INT);
-$elementid = required_param('elementid', PARAM_ALPHANUMEXT);
-$pagehash = required_param('pagehash', PARAM_ALPHANUMEXT);
-$pageinstance = required_param('pageinstance', PARAM_ALPHANUMEXT);
+// Clean up actions.
+$actions = array_map(function($actionparams) {
+ $action = isset($actionparams['action']) ? $actionparams['action'] : null;
+ $params = [];
+ $keys = [
+ 'action' => PARAM_ALPHA,
+ 'contextid' => PARAM_INT,
+ 'elementid' => PARAM_ALPHANUMEXT,
+ 'pagehash' => PARAM_ALPHANUMEXT,
+ 'pageinstance' => PARAM_ALPHANUMEXT
+ ];
+
+ if ($action == 'save') {
+ $keys['drafttext'] = PARAM_RAW;
+ } else if ($action == 'resume') {
+ $keys['draftid'] = PARAM_INT;
+ }
+
+ foreach ($keys as $key => $type) {
+ // Replicate required_param().
+ if (!isset($actionparams[$key])) {
+ print_error('missingparam', '', '', $key);
+ }
+ $params[$key] = clean_param($actionparams[$key], $type);
+ }
+
+ return $params;
+}, isset($_REQUEST['actions']) ? $_REQUEST['actions'] : []);
+
$now = time();
// This is the oldest time any autosave text will be recovered from.
// This is so that there is a good chance the draft files will still exist (there are many variables so
// this is impossible to guarantee).
$before = $now - 60*60*24*4;
-list($context, $course, $cm) = get_context_info_array($contextid);
+$context = context_system::instance();
$PAGE->set_url('/lib/editor/atto/autosave-ajax.php');
$PAGE->set_context($context);
-require_login($course, false, $cm);
-require_sesskey();
-
+require_login();
if (isguestuser()) {
print_error('accessdenied', 'admin');
}
+require_sesskey();
if (!in_array('atto', explode(',', get_config('core', 'texteditors')))) {
print_error('accessdenied', 'admin');
}
-$action = required_param('action', PARAM_ALPHA);
+$responses = array();
+foreach ($actions as $actionparams) {
-$response = array();
+ $action = $actionparams['action'];
+ $contextid = $actionparams['contextid'];
+ $elementid = $actionparams['elementid'];
+ $pagehash = $actionparams['pagehash'];
+ $pageinstance = $actionparams['pageinstance'];
-if ($action === 'save') {
- $drafttext = required_param('drafttext', PARAM_RAW);
- $params = array('elementid' => $elementid,
- 'userid' => $USER->id,
- 'pagehash' => $pagehash,
- 'contextid' => $contextid);
+ if ($action === 'save') {
+ $drafttext = $actionparams['drafttext'];
+ $params = array('elementid' => $elementid,
+ 'userid' => $USER->id,
+ 'pagehash' => $pagehash,
+ 'contextid' => $contextid);
- $record = $DB->get_record('editor_atto_autosave', $params);
- if ($record && $record->pageinstance != $pageinstance) {
- print_error('concurrent access from the same user is not supported');
- die();
- }
-
- if (!$record) {
- $record = new stdClass();
- $record->elementid = $elementid;
- $record->userid = $USER->id;
- $record->pagehash = $pagehash;
- $record->contextid = $contextid;
- $record->drafttext = $drafttext;
- $record->pageinstance = $pageinstance;
- $record->timemodified = $now;
-
- $DB->insert_record('editor_atto_autosave', $record);
-
- // No response means no error.
- die();
- } else {
- $record->drafttext = $drafttext;
- $record->timemodified = time();
- $DB->update_record('editor_atto_autosave', $record);
-
- // No response means no error.
- die();
- }
-} else if ($action == 'resume') {
- $params = array('elementid' => $elementid,
- 'userid' => $USER->id,
- 'pagehash' => $pagehash,
- 'contextid' => $contextid);
-
- $newdraftid = required_param('draftid', PARAM_INT);
-
- $record = $DB->get_record('editor_atto_autosave', $params);
-
- if (!$record) {
- $record = new stdClass();
- $record->elementid = $elementid;
- $record->userid = $USER->id;
- $record->pagehash = $pagehash;
- $record->contextid = $contextid;
- $record->pageinstance = $pageinstance;
- $record->pagehash = $pagehash;
- $record->draftid = $newdraftid;
- $record->timemodified = time();
- $record->drafttext = '';
-
- $DB->insert_record('editor_atto_autosave', $record);
-
- // No response means no error.
- die();
- } else {
- // Copy all draft files from the old draft area.
- $usercontext = context_user::instance($USER->id);
- $stale = $record->timemodified < $before;
- require_once($CFG->libdir . '/filelib.php');
-
- $fs = get_file_storage();
- $files = $fs->get_directory_files($usercontext->id, 'user', 'draft', $newdraftid, '/', true, true);
-
- $lastfilemodified = 0;
- foreach ($files as $file) {
- $lastfilemodified = max($lastfilemodified, $file->get_timemodified());
- }
- if ($record->timemodified < $lastfilemodified) {
- $stale = true;
+ $record = $DB->get_record('editor_atto_autosave', $params);
+ if ($record && $record->pageinstance != $pageinstance) {
+ print_error('concurrent access from the same user is not supported');
+ die();
}
- if (!$stale) {
- // This function copies all the files in one draft area, to another area (in this case it's
- // another draft area). It also rewrites the text to @@PLUGINFILE@@ links.
- $newdrafttext = file_save_draft_area_files($record->draftid,
- $usercontext->id,
- 'user',
- 'draft',
- $newdraftid,
- array(),
- $record->drafttext);
-
- // Final rewrite to the new draft area (convert the @@PLUGINFILES@@ again).
- $newdrafttext = file_rewrite_pluginfile_urls($newdrafttext,
- 'draftfile.php',
- $usercontext->id,
- 'user',
- 'draft',
- $newdraftid);
- $record->drafttext = $newdrafttext;
-
+ if (!$record) {
+ $record = new stdClass();
+ $record->elementid = $elementid;
+ $record->userid = $USER->id;
+ $record->pagehash = $pagehash;
+ $record->contextid = $contextid;
+ $record->drafttext = $drafttext;
$record->pageinstance = $pageinstance;
- $record->draftid = $newdraftid;
+ $record->timemodified = $now;
+
+ $DB->insert_record('editor_atto_autosave', $record);
+
+ // No response means no error.
+ $responses[] = null;
+ continue;
+ } else {
+ $record->drafttext = $drafttext;
$record->timemodified = time();
$DB->update_record('editor_atto_autosave', $record);
- // A response means the draft has been restored and here is the auto-saved text.
- $response['result'] = $record->drafttext;
- echo json_encode($response);
- } else {
- $DB->delete_records('editor_atto_autosave', array('id' => $record->id));
+ // No response means no error.
+ $responses[] = null;
+ continue;
+ }
+
+ } else if ($action == 'resume') {
+ $params = array('elementid' => $elementid,
+ 'userid' => $USER->id,
+ 'pagehash' => $pagehash,
+ 'contextid' => $contextid);
+
+ $newdraftid = $actionparams['draftid'];
+
+ $record = $DB->get_record('editor_atto_autosave', $params);
+
+ if (!$record) {
+ $record = new stdClass();
+ $record->elementid = $elementid;
+ $record->userid = $USER->id;
+ $record->pagehash = $pagehash;
+ $record->contextid = $contextid;
+ $record->pageinstance = $pageinstance;
+ $record->pagehash = $pagehash;
+ $record->draftid = $newdraftid;
+ $record->timemodified = time();
+ $record->drafttext = '';
+
+ $DB->insert_record('editor_atto_autosave', $record);
// No response means no error.
- }
- die();
- }
-} else if ($action == 'reset') {
- $params = array('elementid' => $elementid,
- 'userid' => $USER->id,
- 'pagehash' => $pagehash,
- 'contextid' => $contextid);
+ $responses[] = null;
+ continue;
- $DB->delete_records('editor_atto_autosave', $params);
- die();
+ } else {
+ // Copy all draft files from the old draft area.
+ $usercontext = context_user::instance($USER->id);
+ $stale = $record->timemodified < $before;
+ require_once($CFG->libdir . '/filelib.php');
+
+ $fs = get_file_storage();
+ $files = $fs->get_directory_files($usercontext->id, 'user', 'draft', $newdraftid, '/', true, true);
+
+ $lastfilemodified = 0;
+ foreach ($files as $file) {
+ $lastfilemodified = max($lastfilemodified, $file->get_timemodified());
+ }
+ if ($record->timemodified < $lastfilemodified) {
+ $stale = true;
+ }
+
+ if (!$stale) {
+ // This function copies all the files in one draft area, to another area (in this case it's
+ // another draft area). It also rewrites the text to @@PLUGINFILE@@ links.
+ $newdrafttext = file_save_draft_area_files($record->draftid,
+ $usercontext->id,
+ 'user',
+ 'draft',
+ $newdraftid,
+ array(),
+ $record->drafttext);
+
+ // Final rewrite to the new draft area (convert the @@PLUGINFILES@@ again).
+ $newdrafttext = file_rewrite_pluginfile_urls($newdrafttext,
+ 'draftfile.php',
+ $usercontext->id,
+ 'user',
+ 'draft',
+ $newdraftid);
+ $record->drafttext = $newdrafttext;
+
+ $record->pageinstance = $pageinstance;
+ $record->draftid = $newdraftid;
+ $record->timemodified = time();
+ $DB->update_record('editor_atto_autosave', $record);
+
+ // A response means the draft has been restored and here is the auto-saved text.
+ $response = ['result' => $record->drafttext];
+ $responses[] = $response;
+
+ } else {
+ $DB->delete_records('editor_atto_autosave', array('id' => $record->id));
+
+ // No response means no error.
+ $responses[] = null;
+ }
+ continue;
+ }
+
+ } else if ($action == 'reset') {
+ $params = array('elementid' => $elementid,
+ 'userid' => $USER->id,
+ 'pagehash' => $pagehash,
+ 'contextid' => $contextid);
+
+ $DB->delete_records('editor_atto_autosave', $params);
+ $responses[] = null;
+ continue;
+ }
}
-print_error('invalidarguments');
+echo json_encode($responses);
diff --git a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
index 09ba0b77189..fbcfd8b9e91 100644
--- a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
+++ b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
@@ -786,19 +786,6 @@ EditorAutosave.ATTRS= {
pageHash: {
value: '',
writeOnce: true
- },
-
- /**
- * The relative path to the ajax script.
- *
- * @attribute autosaveAjaxScript
- * @type String
- * @default '/lib/editor/atto/autosave-ajax.php'
- * @readOnly
- */
- autosaveAjaxScript: {
- value: '/lib/editor/atto/autosave-ajax.php',
- readOnly: true
}
};
@@ -839,8 +826,7 @@ EditorAutosave.prototype = {
form,
optiontype = null,
options = this.get('filepickeroptions'),
- params,
- url;
+ params;
if (!this.get('autosaveEnabled')) {
// Autosave disabled for this instance.
@@ -856,99 +842,73 @@ EditorAutosave.prototype = {
// First see if there are any saved drafts.
// Make an ajax request.
- url = M.cfg.wwwroot + this.get('autosaveAjaxScript');
params = {
- sesskey: M.cfg.sesskey,
contextid: this.get('contextid'),
action: 'resume',
- drafttext: '',
draftid: draftid,
elementid: this.get('elementid'),
pageinstance: this.autosaveInstance,
pagehash: this.get('pageHash')
};
- Y.io(url, {
- method: 'POST',
- data: params,
- context: this,
- on: {
- success: function(id,o) {
- var response_json;
- if (typeof o.responseText !== "undefined" && o.responseText !== "") {
- response_json = JSON.parse(o.responseText);
+ this.autosaveIo(params, this, {
+ success: function(response) {
+ if (response === null) {
+ // This can happen when there is nothing to resume from.
+ return;
+ } else if (!response) {
+ Y.log('Invalid response received.', 'debug', LOGNAME_AUTOSAVE);
+ return;
+ }
- // Revert untouched editor contents to an empty string.
- // Check for FF and Chrome.
- if (response_json.result === '
' || response_json.result === '
' ||
- response_json.result === '
') {
- response_json.result = '';
- }
+ // Revert untouched editor contents to an empty string.
+ // Check for FF and Chrome.
+ if (response.result === '' || response.result === '
' ||
+ response.result === '
') {
+ response.result = '';
+ }
- // Check for IE 9 and 10.
- if (response_json.result === '
' || response_json.result === '
') {
- response_json.result = '';
- }
+ // Check for IE 9 and 10.
+ if (response.result === '
' || response.result === '
') {
+ response.result = '';
+ }
- if (response_json.error || typeof response_json.result === 'undefined') {
- Y.log('Error occurred recovering draft text: ' + response_json.error, 'debug', LOGNAME_AUTOSAVE);
- this.showMessage(M.util.get_string('errortextrecovery', 'editor_atto'),
- NOTIFY_WARNING, RECOVER_MESSAGE_TIMEOUT);
- } else if (response_json.result !== this.textarea.get('value') &&
- response_json.result !== '') {
- Y.log('Autosave text found - recover it.', 'debug', LOGNAME_AUTOSAVE);
- this.recoverText(response_json.result);
- }
- this._fireSelectionChanged();
- }
- },
- failure: function() {
+ if (response.error || typeof response.result === 'undefined') {
+ Y.log('Error occurred recovering draft text: ' + response.error, 'debug', LOGNAME_AUTOSAVE);
this.showMessage(M.util.get_string('errortextrecovery', 'editor_atto'),
NOTIFY_WARNING, RECOVER_MESSAGE_TIMEOUT);
+ } else if (response.result !== this.textarea.get('value') &&
+ response.result !== '') {
+ Y.log('Autosave text found - recover it.', 'debug', LOGNAME_AUTOSAVE);
+ this.recoverText(response.result);
}
+ this._fireSelectionChanged();
+
+ },
+ failure: function() {
+ this.showMessage(M.util.get_string('errortextrecovery', 'editor_atto'),
+ NOTIFY_WARNING, RECOVER_MESSAGE_TIMEOUT);
}
});
// Now setup the timer for periodic saves.
-
var delay = parseInt(this.get('autosaveFrequency'), 10) * 1000;
this.autosaveTimer = Y.later(delay, this, this.saveDraft, false, true);
// Now setup the listener for form submission.
form = this.textarea.ancestor('form');
if (form) {
- form.on('submit', this.resetAutosave, this);
+ this.autosaveIoOnSubmit(form, {
+ action: 'reset',
+ contextid: this.get('contextid'),
+ elementid: this.get('elementid'),
+ pageinstance: this.autosaveInstance,
+ pagehash: this.get('pageHash')
+ });
}
return this;
},
- /**
- * Clear the autosave text because the form was submitted normally.
- *
- * @method resetAutosave
- * @chainable
- */
- resetAutosave: function() {
- // Make an ajax request to reset the autosaved text.
- var url = M.cfg.wwwroot + this.get('autosaveAjaxScript');
- var params = {
- sesskey: M.cfg.sesskey,
- contextid: this.get('contextid'),
- action: 'reset',
- elementid: this.get('elementid'),
- pageinstance: this.autosaveInstance,
- pagehash: this.get('pageHash')
- };
-
- Y.io(url, {
- method: 'POST',
- data: params,
- sync: true
- });
- return this;
- },
-
-
/**
* Recover a previous version of this text and show a message.
*
@@ -1004,29 +964,23 @@ EditorAutosave.prototype = {
};
// Reusable error handler - must be passed the correct context.
- var ajaxErrorFunction = function(code, response) {
+ var ajaxErrorFunction = function(response) {
var errorDuration = parseInt(this.get('autosaveFrequency'), 10) * 1000;
- Y.log('Error while autosaving text:' + code, 'warn', LOGNAME_AUTOSAVE);
+ Y.log('Error while autosaving text', 'warn', LOGNAME_AUTOSAVE);
Y.log(response, 'warn', LOGNAME_AUTOSAVE);
this.showMessage(M.util.get_string('autosavefailed', 'editor_atto'), NOTIFY_WARNING, errorDuration);
};
- Y.io(url, {
- method: 'POST',
- data: params,
- context: this,
- on: {
- error: ajaxErrorFunction,
- failure: ajaxErrorFunction,
- success: function(code, response) {
- if (response.responseText !== "") {
- Y.soon(Y.bind(ajaxErrorFunction, this, [code, response]));
- } else {
- // All working.
- this.lastText = newText;
- this.showMessage(M.util.get_string('autosavesucceeded', 'editor_atto'),
- NOTIFY_INFO, SUCCESS_MESSAGE_TIMEOUT);
- }
+ this.autosaveIo(params, this, {
+ failure: ajaxErrorFunction,
+ success: function(response) {
+ if (response && response.error) {
+ Y.soon(Y.bind(ajaxErrorFunction, this, [response]));
+ } else {
+ // All working.
+ this.lastText = newText;
+ this.showMessage(M.util.get_string('autosavesucceeded', 'editor_atto'),
+ NOTIFY_INFO, SUCCESS_MESSAGE_TIMEOUT);
}
}
});
@@ -1051,6 +1005,250 @@ Y.Base.mix(Y.M.editor_atto.Editor, [EditorAutosave]);
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
+/**
+ * A autosave function for the Atto editor.
+ *
+ * @module moodle-editor_atto-autosave-io
+ * @submodule autosave-io
+ * @package editor_atto
+ * @copyright 2016 Frédéric Massart
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+var EditorAutosaveIoDispatcherInstance = null;
+
+function EditorAutosaveIoDispatcher() {
+ EditorAutosaveIoDispatcher.superclass.constructor.apply(this, arguments);
+ this._submitEvents = {};
+ this._queue = [];
+ this._throttle = null;
+}
+EditorAutosaveIoDispatcher.NAME = 'EditorAutosaveIoDispatcher';
+EditorAutosaveIoDispatcher.ATTRS = {
+
+ /**
+ * The relative path to the ajax script.
+ *
+ * @attribute autosaveAjaxScript
+ * @type String
+ * @default '/lib/editor/atto/autosave-ajax.php'
+ * @readOnly
+ */
+ autosaveAjaxScript: {
+ value: '/lib/editor/atto/autosave-ajax.php',
+ readOnly: true
+ },
+
+ /**
+ * The time buffer for the throttled requested.
+ *
+ * @attribute delay
+ * @type Number
+ * @default 50
+ * @readOnly
+ */
+ delay: {
+ value: 50,
+ readOnly: true
+ }
+
+};
+Y.extend(EditorAutosaveIoDispatcher, Y.Base, {
+
+ /**
+ * Dispatch an IO request.
+ *
+ * This method will put the requests in a queue in order to attempt to bulk them.
+ *
+ * @param {Object} params The parameters of the request.
+ * @param {Object} context The context in which the callbacks are called.
+ * @param {Object} callbacks Object with 'success', 'complete', 'end', 'failure' and 'start' as
+ * optional keys defining the callbacks to call. Success and Complete
+ * functions will receive the response as parameter. Success and Complete
+ * may receive an object containing the error key, use this to confirm
+ * that no errors occured.
+ * @return {Void}
+ */
+ dispatch: function(params, context, callbacks) {
+ if (this._throttle) {
+ this._throttle.cancel();
+ }
+
+ this._throttle = Y.later(this.get('delay'), this, this._processDispatchQueue);
+ this._queue.push([params, context, callbacks]);
+ },
+
+ /**
+ * Dispatches the requests in the queue.
+ *
+ * @return {Void}
+ */
+ _processDispatchQueue: function() {
+ var queue = this._queue,
+ data = {};
+
+ this._queue = [];
+ if (queue.length < 1) {
+ return;
+ }
+
+ Y.Array.each(queue, function(item, index) {
+ data[index] = item[0];
+ });
+
+ Y.io(M.cfg.wwwroot + this.get('autosaveAjaxScript'), {
+ method: 'POST',
+ data: Y.QueryString.stringify({
+ actions: data,
+ sesskey: M.cfg.sesskey
+ }),
+ on: {
+ start: this._makeIoEventCallback('start', queue),
+ complete: this._makeIoEventCallback('complete', queue),
+ failure: this._makeIoEventCallback('failure', queue),
+ end: this._makeIoEventCallback('end', queue),
+ success: this._makeIoEventCallback('success', queue)
+ }
+ });
+ },
+
+ /**
+ * Creates a function that dispatches an IO response to callbacks.
+ *
+ * @param {String} event The type of event.
+ * @param {Array} queue The queue.
+ * @return {Function}
+ */
+ _makeIoEventCallback: function(event, queue) {
+ var noop = function() {};
+ return function() {
+ var response = arguments[1],
+ parsed = {};
+
+ if ((event == 'complete' || event == 'success') && (typeof response !== 'undefined'
+ && typeof response.responseText !== 'undefined' && response.responseText !== '')) {
+
+ // Success and complete events need to parse the response.
+ parsed = JSON.parse(response.responseText) || {};
+ }
+
+ Y.Array.each(queue, function(item, index) {
+ var context = item[1],
+ cb = (item[2] && item[2][event]) || noop,
+ arg;
+
+ if (parsed && parsed.error) {
+ // The response is an error, we send it to everyone.
+ arg = parsed;
+ } else if (parsed) {
+ // The response was parsed, we only communicate the relevant portion of the response.
+ arg = parsed[index];
+ }
+
+ cb.apply(context, [arg]);
+ });
+ };
+ },
+
+ /**
+ * Form submit handler.
+ *
+ * @param {EventFacade} e The event.
+ * @return {Void}
+ */
+ _onSubmit: function(e) {
+ var data = {},
+ id = e.currentTarget.generateID(),
+ params = this._submitEvents[id];
+
+ if (!params || params.ios.length < 1) {
+ return;
+ }
+
+ Y.Array.each(params.ios, function(param, index) {
+ data[index] = param;
+ });
+
+ Y.io(M.cfg.wwwroot + this.get('autosaveAjaxScript'), {
+ method: 'POST',
+ data: Y.QueryString.stringify({
+ actions: data,
+ sesskey: M.cfg.sesskey
+ }),
+ sync: true
+ });
+ },
+
+ /**
+ * Registers a request to be made on form submission.
+ *
+ * @param {Node} node The forum node we will listen to.
+ * @param {Object} params Parameters for the IO request.
+ * @return {Void}
+ */
+ whenSubmit: function(node, params) {
+ if (typeof this._submitEvents[node.generateID()] === 'undefined') {
+ this._submitEvents[node.generateID()] = {
+ event: node.on('submit', this._onSubmit, this),
+ ios: []
+ };
+ }
+ this._submitEvents[node.get('id')].ios.push([params]);
+ }
+
+});
+EditorAutosaveIoDispatcherInstance = new EditorAutosaveIoDispatcher();
+
+
+function EditorAutosaveIo() {}
+EditorAutosaveIo.prototype = {
+
+ /**
+ * Dispatch an IO request.
+ *
+ * This method will put the requests in a queue in order to attempt to bulk them.
+ *
+ * @param {Object} params The parameters of the request.
+ * @param {Object} context The context in which the callbacks are called.
+ * @param {Object} callbacks Object with 'success', 'complete', 'end', 'failure' and 'start' as
+ * optional keys defining the callbacks to call. Success and Complete
+ * functions will receive the response as parameter. Success and Complete
+ * may receive an object containing the error key, use this to confirm
+ * that no errors occured.
+ * @return {Void}
+ */
+ autosaveIo: function(params, context, callbacks) {
+ EditorAutosaveIoDispatcherInstance.dispatch(params, context, callbacks);
+ },
+
+ /**
+ * Registers a request to be made on form submission.
+ *
+ * @param {Node} form The forum node we will listen to.
+ * @param {Object} params Parameters for the IO request.
+ * @return {Void}
+ */
+ autosaveIoOnSubmit: function(form, params) {
+ EditorAutosaveIoDispatcherInstance.whenSubmit(form, params);
+ }
+
+};
+Y.Base.mix(Y.M.editor_atto.Editor, [EditorAutosaveIo]);
+// 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 .
+
/**
* @module moodle-editor_atto-editor
* @submodule clean
@@ -2606,6 +2804,7 @@ Y.Base.mix(Y.M.editor_atto.Editor, [EditorFilepicker]);
"moodle-core-notification-confirm",
"moodle-editor_atto-rangy",
"handlebars",
- "timers"
+ "timers",
+ "querystring-stringify"
]
});
diff --git a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
index 912122eba5c..c8557d0e7cc 100644
--- a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
+++ b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
@@ -1,4 +1,4 @@
-YUI.add("moodle-editor_atto-editor",function(e,t){function s(){s.superclass.constructor.apply(this,arguments)}function f(){}function l(){}function d(){}function v(){}function m(){}function g(){}function y(){}function b(){}function w(){}function E(){}var n="moodle-editor_atto-editor",r={CONTENT:"editor_atto_content",CONTENTWRAPPER:"editor_atto_content_wrap",TOOLBAR:"editor_atto_toolbar",WRAPPER:"editor_atto",HIGHLIGHT:"highlight"},i=window.rangy;e.extend(s,e.Base,{BLOCK_TAGS:["address","article","aside","audio","blockquote","canvas","dd","div","dl","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","hr","noscript","ol","output","p","pre","section","table","tfoot","ul","video"],PLACEHOLDER_CLASS:"atto-tmp-class",ALL_NODES_SELECTOR:"[style],font[face]",FONT_FAMILY:"fontFamily",_wrapper:null,editor:null,textarea:null,textareaLabel:null,plugins:null,_eventHandles:null,initializer:function(){var t;this.textarea=e.one(document.getElementById(this.get("elementid")));if(!this.textarea)return;this._eventHandles=[],this._wrapper=e.Node.create(''),t=e.Handlebars.compile(''),this.editor=e.Node.create(t({elementid:this.get("elementid"),CSS:r})),this.textareaLabel=e.one('[for="'+this.get("elementid")+'"]'),this.textareaLabel&&(this.textareaLabel.generateID(),this.editor.setAttribute("aria-labelledby",this.textareaLabel.get("id"))),this.setupToolbar();var n=e.Node.create('');n.appendChild(this.editor),this._wrapper.appendChild(n),this.editor.setStyle("minHeight",20*this.textarea.getAttribute("rows")+8+"px"),e.UA.ie===0&&this.editor.setStyle("height",20*this.textarea.getAttribute("rows")+8+"px"),this.disableCssStyling(),document.queryCommandSupported("DefaultParagraphSeparator")&&document.execCommand("DefaultParagraphSeparator",!1,"p"),this.textarea.get("parentNode").insert(this._wrapper,this.textarea).setAttribute("class","editor_atto_wrap"),this.textarea.hide(),this.updateFromTextArea(),this.publishEvents(),this.setupSelectionWatchers(),this.setupAutomaticPolling(),this.setupPlugins(),this.setupAutosave(),this.setupNotifications()},focus:function(){return this.editor.focus(),this},publishEvents:function(){return this.publish("change",{broadcast:!0,preventable:!0}),this.publish("pluginsloaded",{fireOnce:!0}),this.publish("atto:selectionchanged",{prefix:"atto"}),this},setupAutomaticPolling:function(){return this._registerEventHandle(this.editor.on(["keyup","cut"],this.updateOriginal,this)),this._registerEventHandle(this.editor.on("paste",this.pasteCleanup,this)),this._registerEventHandle(this.editor.on("drop",this.updateOriginalDelayed,this)),this},updateOriginalDelayed:function(){return e.soon(e.bind(this.updateOriginal,this)),this},setupPlugins:function(){this.plugins={};var t=this.get("plugins"),n,r,i,s,o;for(n in t){r=t[n];if(!r.plugins)continue;for(i in r.plugins){s=r.plugins[i],o=e.mix({name:s.name,group:r.group,editor:this.editor,toolbar:this.toolbar,host:this},s);if(typeof e.M["atto_"+s.name]=="undefined")continue;this.plugins[s.name]=new e.M["atto_"+s.name].Button(o)}}return this.fire("pluginsloaded"),this},enablePlugins:function(e){this._setPluginState(!0,e)},disablePlugins:function(e){this._setPluginState(!1,e)},_setPluginState:function(t,n){var r="disableButtons";t&&(r="enableButtons"),n?this.plugins[n][r]():e.Object.each(this.plugins,function(e){e[r]()},this)},_registerEventHandle:function(e){this._eventHandles.push(e)}},{NS:"editor_atto",ATTRS:{elementid:{value:null,writeOnce:!0},contextid:{value:null,writeOnce:!0},plugins:{value:{},writeOnce:!0}}}),e.augment(s,e.EventTarget),e.namespace("M.editor_atto").Editor=s,e.namespace("M.editor_atto.Editor").init=function(t){return new e.M.editor_atto.Editor(t)};var o="moodle-editor_atto-editor-notify",u="info",a="warning";f.ATTRS={},f.prototype={messageOverlay:null,hideTimer:null,setupNotifications:function(){var e=new Image,t=new Image;return e.src=M.util.image_url("i/warning","moodle"),t.src=M.util.image_url("i/info","moodle"),this},showMessage:function(t,n,r){var i="",s,o;return this.messageOverlay===null&&(this.messageOverlay=e.Node.create(''),this.messageOverlay.hide(!0),this.textarea.get("parentNode").append(this.messageOverlay),this.messageOverlay.on("click",function(){this.messageOverlay.hide(!0)},this)),this.hideTimer!==null&&this.hideTimer.cancel(),n===a?i='
':n===u&&(i='
'),s=parseInt(r,10),s<=0&&(s=6e4),n="atto_"+n,o=e.Node.create(''+i+" "+e.Escape.html(t)+"
"),this.messageOverlay.empty(),this.messageOverlay.append(o),this.messageOverlay.show(!0),this.hideTimer=e.later(s,this,function(){this.hideTimer=null,this.messageOverlay.hide(!0)}),this}},e.Base.mix(e.M.editor_atto.Editor,[f]),l.ATTRS={},l.prototype={_getEmptyContent:function(){return e.UA.ie&&e.UA.ie<10?"":"
"},updateFromTextArea:function(){return this.editor.setHTML(""),this.editor.append(this._cleanHTML(this.textarea.get("value"))),this.editor.getHTML()===""&&this.editor.setHTML(this._getEmptyContent()),this},updateOriginal:function(){var e=this.textarea.get("value"),t=this.getCleanHTML();return t===""&&this.isActive()&&(t=this._getEmptyContent()),e!==t&&(this.textarea.set("value",t),this.textarea.simulate("change"),this.fire("change")),this}},e.Base.mix(e.M.editor_atto.Editor,[l]);var c=5e3,h=6e4,p="moodle-editor_atto-editor-autosave";d.ATTRS={autosaveEnabled:{value:!0,writeOnce:!0},autosaveFrequency:{value:60,writeOnce:!0},pageHash:{value:"",writeOnce:!0},autosaveAjaxScript:{value:"/lib/editor/atto/autosave-ajax.php",readOnly:!0}}
-,d.prototype={lastText:"",autosaveInstance:null,autosaveTimer:null,setupAutosave:function(){var t=-1,n,r=null,i=this.get("filepickeroptions"),s,o;if(!this.get("autosaveEnabled"))return;this.autosaveInstance=e.stamp(this);for(r in i)typeof i[r].itemid!="undefined"&&(t=i[r].itemid);o=M.cfg.wwwroot+this.get("autosaveAjaxScript"),s={sesskey:M.cfg.sesskey,contextid:this.get("contextid"),action:"resume",drafttext:"",draftid:t,elementid:this.get("elementid"),pageinstance:this.autosaveInstance,pagehash:this.get("pageHash")},e.io(o,{method:"POST",data:s,context:this,on:{success:function(e,t){var n;if(typeof t.responseText!="undefined"&&t.responseText!==""){n=JSON.parse(t.responseText);if(n.result===""||n.result==="
"||n.result==="
")n.result="";if(n.result==="
"||n.result==="
")n.result="";n.error||typeof n.result=="undefined"?this.showMessage(M.util.get_string("errortextrecovery","editor_atto"),a,h):n.result!==this.textarea.get("value")&&n.result!==""&&this.recoverText(n.result),this._fireSelectionChanged()}},failure:function(){this.showMessage(M.util.get_string("errortextrecovery","editor_atto"),a,h)}}});var u=parseInt(this.get("autosaveFrequency"),10)*1e3;return this.autosaveTimer=e.later(u,this,this.saveDraft,!1,!0),n=this.textarea.ancestor("form"),n&&n.on("submit",this.resetAutosave,this),this},resetAutosave:function(){var t=M.cfg.wwwroot+this.get("autosaveAjaxScript"),n={sesskey:M.cfg.sesskey,contextid:this.get("contextid"),action:"reset",elementid:this.get("elementid"),pageinstance:this.autosaveInstance,pagehash:this.get("pageHash")};return e.io(t,{method:"POST",data:n,sync:!0}),this},recoverText:function(e){return this.editor.setHTML(e),this.saveSelection(),this.updateOriginal(),this.lastText=e,this.showMessage(M.util.get_string("textrecovered","editor_atto"),u,h),this},saveDraft:function(){var t,n;if(!this.editor.getDOMNode()){this.autosaveTimer.cancel();return}this.editor.get("hidden")||this.updateOriginal();var r=this.textarea.get("value");if(r!==this.lastText){t=M.cfg.wwwroot+this.get("autosaveAjaxScript"),n={sesskey:M.cfg.sesskey,contextid:this.get("contextid"),action:"save",drafttext:r,elementid:this.get("elementid"),pagehash:this.get("pageHash"),pageinstance:this.autosaveInstance};var i=function(e,t){var n=parseInt(this.get("autosaveFrequency"),10)*1e3;this.showMessage(M.util.get_string("autosavefailed","editor_atto"),a,n)};e.io(t,{method:"POST",data:n,context:this,on:{error:i,failure:i,success:function(t,n){n.responseText!==""?e.soon(e.bind(i,this,[t,n])):(this.lastText=r,this.showMessage(M.util.get_string("autosavesucceeded","editor_atto"),u,c))}}})}return this}},e.Base.mix(e.M.editor_atto.Editor,[d]),v.ATTRS={},v.prototype={getCleanHTML:function(){var t=this.editor.cloneNode(!0),n;return e.each(t.all('[id^="yui"]'),function(e){e.removeAttribute("id")}),t.all(".atto_control").remove(!0),n=t.get("innerHTML"),n===""||n==="
"?"":this._cleanHTML(n)},cleanEditorHTML:function(){var e=this.editor.get("innerHTML");return this.editor.set("innerHTML",this._cleanHTML(e)),this},_cleanHTML:function(e){var t=[{regex:/