From 0f9489f9fed14e4bbf7c2008dda65aa6515a364c Mon Sep 17 00:00:00 2001
From: webchick <webchick@24967.no-reply.drupal.org>
Date: Fri, 18 Jan 2013 10:00:29 -0800
Subject: [PATCH] Issue #1875632 follow-up by nod_, Wim Leers, effulgentsia:
 Added wrapper function for JS settings merging behavior, fix tabledrag.

---
 core/includes/ajax.inc                        |  6 +-
 core/includes/common.inc                      | 64 ++++++++++++++++++-
 core/lib/Drupal/Core/Ajax/AjaxResponse.php    |  5 +-
 core/modules/file/file.module                 |  2 +-
 .../lib/Drupal/simpletest/WebTestBase.php     |  2 +-
 5 files changed, 68 insertions(+), 11 deletions(-)

diff --git a/core/includes/ajax.inc b/core/includes/ajax.inc
index a7fbe7bb124f..d8eb913c7715 100644
--- a/core/includes/ajax.inc
+++ b/core/includes/ajax.inc
@@ -5,8 +5,6 @@
  * Functions for use with Drupal's Ajax framework.
  */
 
-use Drupal\Component\Utility\NestedArray;
-
 /**
  * @defgroup ajax Ajax framework
  * @{
@@ -291,8 +289,8 @@ function ajax_render($commands = array()) {
   // Now add a command to merge changes and additions to Drupal.settings.
   $scripts = drupal_add_js();
   if (!empty($scripts['settings'])) {
-    $settings = $scripts['settings'];
-    array_unshift($commands, ajax_command_settings(NestedArray::mergeDeepArray($settings['data']), TRUE));
+    $settings = drupal_merge_js_settings($scripts['settings']['data']);
+    array_unshift($commands, ajax_command_settings($settings, TRUE));
   }
 
   // Allow modules to alter any Ajax response.
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 2638468f44e2..fef91758fe3a 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -3868,6 +3868,63 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
   return drupal_render($elements);
 }
 
+/**
+ * Merges an array of settings arrays into a single settings array.
+ *
+ * This function merges the items in the same way that
+ *
+ * @code
+ *   jQuery.extend(true, {}, $settings_items[0], $settings_items[1], ...)
+ * @endcode
+ *
+ * would. This means integer indeces are preserved just like string indeces are,
+ * rather than re-indexed as is common in PHP array merging.
+ *
+ * Example:
+ * @code
+ * function module1_page_build(&$page) {
+ *   $page['#attached']['js'][] = array(
+ *     'type' => 'setting',
+ *     'data' => array('foo' => array('a', 'b', 'c')),
+ *   );
+ * }
+ * function module2_page_build(&$page) {
+ *   $page['#attached']['js'][] = array(
+ *     'type' => 'setting',
+ *     'data' => array('foo' => array('d')),
+ *   );
+ * }
+ * // When the page is rendered after the above code, and the browser runs the
+ * // resulting <SCRIPT> tags, the value of drupalSettings.foo is
+ * // ['d', 'b', 'c'], not ['a', 'b', 'c', 'd'].
+ * @endcode
+ *
+ * By following jQuery.extend() merge logic rather than common PHP array merge
+ * logic, the following are ensured:
+ * - drupal_add_js() is idempotent: calling it twice with the same parameters
+ *   does not change the output sent to the browser.
+ * - If pieces of the page are rendered in separate PHP requests and the
+ *   returned settings are merged by JavaScript, the resulting settings are the
+ *   same as if rendered in one PHP request and merged by PHP.
+ *
+ * @param $settings_items
+ *   An array of settings arrays, as returned by:
+ *   @code
+ *     $js = drupal_add_js();
+ *     $settings_items = $js['settings']['data'];
+ *   @endcode
+ *
+ * @return
+ *   A merged $settings array, suitable for JSON encoding and returning to the
+ *   browser.
+ *
+ * @see drupal_add_js()
+ * @see drupal_pre_render_scripts()
+ */
+function drupal_merge_js_settings($settings_items) {
+  return NestedArray::mergeDeepArray($settings_items, TRUE);
+}
+
 /**
  * #pre_render callback to add the elements needed for JavaScript tags to be rendered.
  *
@@ -3949,7 +4006,7 @@ function drupal_pre_render_scripts($elements) {
         switch ($item['type']) {
           case 'setting':
             $element['#value_prefix'] = $embed_prefix;
-            $element['#value'] = 'var drupalSettings = ' . drupal_json_encode(NestedArray::mergeDeepArray($item['data'], TRUE)) . ";";
+            $element['#value'] = 'var drupalSettings = ' . drupal_json_encode(drupal_merge_js_settings($item['data'])) . ";";
             $element['#value_suffix'] = $embed_suffix;
             break;
 
@@ -4574,6 +4631,9 @@ function drupal_get_library($module, $name = NULL) {
  */
 function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgroup = NULL, $source = NULL, $hidden = TRUE, $limit = 0) {
   $js_added = &drupal_static(__FUNCTION__, FALSE);
+  $tabledrag_id = &drupal_static(__FUNCTION__  . '_setting', FALSE);
+  $tabledrag_id = (!isset($tabledrag_id)) ? 0 : $tabledrag_id + 1;
+
   if (!$js_added) {
     // Add the table drag JavaScript to the page before the module JavaScript
     // to ensure that table drag behaviors are registered before any module
@@ -4585,7 +4645,7 @@ function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgro
   // If a subgroup or source isn't set, assume it is the same as the group.
   $target = isset($subgroup) ? $subgroup : $group;
   $source = isset($source) ? $source : $target;
-  $settings['tableDrag'][$table_id][$group][] = array(
+  $settings['tableDrag'][$table_id][$group][$tabledrag_id] = array(
     'target' => $target,
     'source' => $source,
     'relationship' => $relationship,
diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponse.php b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
index d87c4a625569..111c1ed1a26a 100644
--- a/core/lib/Drupal/Core/Ajax/AjaxResponse.php
+++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
@@ -9,7 +9,6 @@
 
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
-use Drupal\Component\Utility\NestedArray;
 
 /**
  * JSON response object for AJAX requests.
@@ -134,8 +133,8 @@ protected function ajaxRender(Request $request) {
     // Prepend a command to merge changes and additions to Drupal.settings.
     $scripts = drupal_add_js();
     if (!empty($scripts['settings'])) {
-      $settings = $scripts['settings'];
-      $this->addCommand(new SettingsCommand(NestedArray::mergeDeepArray($settings['data']), TRUE), TRUE);
+      $settings = drupal_merge_js_settings($scripts['settings']['data']);
+      $this->addCommand(new SettingsCommand($settings, TRUE));
     }
 
     $commands = $this->commands;
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 5f56a9fc7dd3..6481d6026280 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -776,7 +776,7 @@ function file_ajax_upload() {
 
   $output = theme('status_messages') . drupal_render($form);
   $js = drupal_add_js();
-  $settings = NestedArray::mergeDeepArray($js['settings']['data']);
+  $settings = drupal_merge_js_settings($js['settings']['data']);
 
   $response = new AjaxResponse();
   return $response->addCommand(new ReplaceCommand(NULL, $output, $settings));
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
index 5f2a3eeb5840..5a1f21207786 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -1404,7 +1404,7 @@ protected function drupalPostAJAX($path, $edit, $triggering_element, $ajax_path
       foreach ($return as $command) {
         switch ($command['command']) {
           case 'settings':
-            $drupal_settings = NestedArray::mergeDeep($drupal_settings, $command['settings']);
+            $drupal_settings = drupal_merge_js_settings(array($drupal_settings, $command['settings']));
             break;
 
           case 'insert':
-- 
GitLab