summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAngie Byron <webchick@24967.no-reply.drupal.org>2010-12-23 04:26:31 +0000
committerAngie Byron <webchick@24967.no-reply.drupal.org>2010-12-23 04:26:31 +0000
commite658064759d54e0d43872a5430ad0381366b0bcd (patch)
tree319818090c7100a0c6a7c7912a532f767d664d6f
parentbb344c82d5c61f9259d54c247aef26f71419022a (diff)
downloadbrdo-e658064759d54e0d43872a5430ad0381366b0bcd.tar.gz
brdo-e658064759d54e0d43872a5430ad0381366b0bcd.tar.bz2
#995854 by rfay, effulgentsia, sun, merlinofchaos, Damien Tournoud, manimejia: Fixed #ajax doesn't work at all if a file element (or enctype => 'multipart/form-data') is included in the form
-rw-r--r--includes/ajax.inc57
-rw-r--r--misc/ajax.js19
-rw-r--r--modules/file/file.module6
3 files changed, 55 insertions, 27 deletions
diff --git a/includes/ajax.inc b/includes/ajax.inc
index af7e6da22..a119e1770 100644
--- a/includes/ajax.inc
+++ b/includes/ajax.inc
@@ -407,19 +407,49 @@ function ajax_base_page_theme() {
/**
* Package and send the result of a page callback to the browser as an AJAX response.
*
+ * This function is the equivalent of drupal_deliver_html_page(), but for AJAX
+ * requests. Like that function, it:
+ * - Adds needed HTTP headers.
+ * - Prints rendered output.
+ * - Performs end-of-request tasks.
+ *
* @param $page_callback_result
* The result of a page callback. Can be one of:
* - NULL: to indicate no content.
* - An integer menu status constant: to indicate an error condition.
* - A string of HTML content.
* - A renderable array of content.
+ *
+ * @see drupal_deliver_html_page()
*/
function ajax_deliver($page_callback_result) {
- $commands = array();
- $header = TRUE;
+ // Emit a Content-Type HTTP header if none has been added by the page callback
+ // or by a wrapping delivery callback.
+ if (is_null(drupal_get_http_header('Content-Type'))) {
+ // The standard header for JSON is application/json.
+ // @see http://www.ietf.org/rfc/rfc4627.txt?number=4627
+ // However, browsers do not allow JavaScript to read the contents of a
+ // user's local files. To work around that, jQuery submits forms containing
+ // a file input element to an IFRAME, instead of using XHR.
+ // @see http://malsup.com/jquery/form/#file-upload
+ // When Internet Explorer receives application/json content in an IFRAME, it
+ // treats it as a file download and prompts the user to save it. To prevent
+ // that, we return the content as text/plain. But only for POST requests,
+ // since jQuery should always use XHR for GET requests and the incorrect
+ // mime type should not end up in page or proxy server caches.
+ // @see http://drupal.org/node/995854
+ $iframe_upload = !isset($_SERVER['HTTP_X_REQUESTED_WITH']) || $_SERVER['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest';
+ if ($iframe_upload && $_SERVER['REQUEST_METHOD'] == 'POST') {
+ drupal_add_http_header('Content-Type', 'text/plain; charset=utf-8');
+ }
+ else {
+ drupal_add_http_header('Content-Type', 'application/json; charset=utf-8');
+ }
+ }
// Normalize whatever was returned by the page callback to an AJAX commands
// array.
+ $commands = array();
if (!isset($page_callback_result)) {
// Simply delivering an empty commands array is sufficient. This results
// in the AJAX request being completed, but nothing being done to the page.
@@ -444,7 +474,6 @@ function ajax_deliver($page_callback_result) {
// Complex AJAX callbacks can return a result that contains an error message
// or a specific set of commands to send to the browser.
$page_callback_result += element_info('ajax');
- $header = $page_callback_result['#header'];
$error = $page_callback_result['#error'];
if (isset($error) && $error !== FALSE) {
if ((empty($error) || $error === TRUE)) {
@@ -470,24 +499,10 @@ function ajax_deliver($page_callback_result) {
$commands[] = ajax_command_prepend(NULL, theme('status_messages'));
}
- // This function needs to do the same thing that drupal_deliver_html_page()
- // does: add any needed http headers, print rendered output, and perform
- // end-of-request tasks. By default, $header=TRUE, and we add a
- // 'text/javascript' header. The page callback can override $header by
- // returning an 'ajax' element with a #header property. This can be set to
- // FALSE to prevent the 'text/javascript' header from being output, necessary
- // when outputting to an IFRAME. This can also be set to 'multipart', in which
- // case, we don't output JSON, but JSON content wrapped in a textarea, making
- // a 'text/javascript' header incorrect.
- if ($header && $header !== 'multipart') {
- drupal_add_http_header('Content-Type', 'text/javascript; charset=utf-8');
- }
- $output = ajax_render($commands);
- if ($header === 'multipart') {
- // jQuery file uploads: http://malsup.com/jquery/form/#code-samples
- $output = '<textarea>' . $output . '</textarea>';
- }
- print $output;
+ // Unlike the recommendation in http://malsup.com/jquery/form/#file-upload,
+ // we do not have to wrap the JSON string in a TEXTAREA, because
+ // drupal_json_encode() returns an HTML-safe JSON string.
+ print ajax_render($commands);
ajax_footer();
}
diff --git a/misc/ajax.js b/misc/ajax.js
index 57a16048e..b17e64a9f 100644
--- a/misc/ajax.js
+++ b/misc/ajax.js
@@ -152,9 +152,9 @@ Drupal.ajax = function (base, element, element_settings) {
ajax.ajaxing = true;
return ajax.beforeSubmit(form_values, element_settings, options);
},
- beforeSend: function (xmlhttprequest) {
+ beforeSend: function (xmlhttprequest, options) {
ajax.ajaxing = true;
- return ajax.beforeSend(xmlhttprequest, ajax.options);
+ return ajax.beforeSend(xmlhttprequest, options);
},
success: function (response, status) {
// Sanity check for browser support (object expected).
@@ -318,7 +318,20 @@ Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) {
* Prepare the AJAX request before it is sent.
*/
Drupal.ajax.prototype.beforeSend = function (xmlhttprequest, options) {
- // Disable the element that received the change.
+ // Disable the element that received the change to prevent user interface
+ // interaction while the AJAX request is in progress. ajax.ajaxing prevents
+ // the element from triggering a new request, but does not prevent the user
+ // from changing its value.
+ // Forms without file inputs are already serialized before this function is
+ // called. Forms with file inputs use an IFRAME to perform a POST request
+ // similar to a browser, so disabled elements are not contained in the
+ // submitted values. Therefore, we manually add the element's value to
+ // options.extraData.
+ var v = $.fieldValue(this.element);
+ if (v !== null) {
+ options.extraData = options.extraData || {};
+ options.extraData[this.element.name] = v;
+ }
$(this.element).addClass('progress-disabled').attr('disabled', true);
// Insert progressbar or throbber.
diff --git a/modules/file/file.module b/modules/file/file.module
index 81c6000e5..bbf3a1ae4 100644
--- a/modules/file/file.module
+++ b/modules/file/file.module
@@ -237,7 +237,7 @@ function file_ajax_upload() {
drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error');
$commands = array();
$commands[] = ajax_command_replace(NULL, theme('status_messages'));
- return array('#type' => 'ajax', '#commands' => $commands, '#header' => FALSE);
+ return array('#type' => 'ajax', '#commands' => $commands);
}
list($form, $form_state) = ajax_get_form();
@@ -247,7 +247,7 @@ function file_ajax_upload() {
drupal_set_message(t('An unrecoverable error occurred. Use of this form has expired. Try reloading the page and submitting again.'), 'error');
$commands = array();
$commands[] = ajax_command_replace(NULL, theme('status_messages'));
- return array('#type' => 'ajax', '#commands' => $commands, '#header' => FALSE);
+ return array('#type' => 'ajax', '#commands' => $commands);
}
// Get the current element and count the number of files.
@@ -280,7 +280,7 @@ function file_ajax_upload() {
$commands = array();
$commands[] = ajax_command_replace(NULL, $output, $settings);
- return array('#type' => 'ajax', '#commands' => $commands, '#header' => FALSE);
+ return array('#type' => 'ajax', '#commands' => $commands);
}
/**