From e658064759d54e0d43872a5430ad0381366b0bcd Mon Sep 17 00:00:00 2001
From: Angie Byron <webchick@24967.no-reply.drupal.org>
Date: Thu, 23 Dec 2010 04:26:31 +0000
Subject: [PATCH] #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

---
 includes/ajax.inc        | 57 +++++++++++++++++++++++++---------------
 misc/ajax.js             | 19 +++++++++++---
 modules/file/file.module |  6 ++---
 3 files changed, 55 insertions(+), 27 deletions(-)

diff --git a/includes/ajax.inc b/includes/ajax.inc
index af7e6da22a10..a119e17702ed 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 57a16048e7ea..b17e64a9f7e3 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 81c6000e5b6f..bbf3a1ae414f 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);
 }
 
 /**
-- 
GitLab