From 95feb071fb1393747df59e2c04b39320160d9b06 Mon Sep 17 00:00:00 2001
From: Dries <dries@buytaert.net>
Date: Fri, 16 Nov 2012 18:05:11 -0800
Subject: [PATCH] Issue #1812866 by mkadin, effulgentsia: rebuild the server
 side AJAX API.

---
 core/lib/Drupal/Core/Ajax/AddCssCommand.php   |  53 +++
 core/lib/Drupal/Core/Ajax/AfterCommand.php    |  40 +++
 core/lib/Drupal/Core/Ajax/AjaxResponse.php    | 131 ++++++++
 core/lib/Drupal/Core/Ajax/AlertCommand.php    |  45 +++
 core/lib/Drupal/Core/Ajax/AppendCommand.php   |  40 +++
 core/lib/Drupal/Core/Ajax/BeforeCommand.php   |  40 +++
 core/lib/Drupal/Core/Ajax/ChangedCommand.php  |  65 ++++
 .../lib/Drupal/Core/Ajax/CommandInterface.php |  22 ++
 core/lib/Drupal/Core/Ajax/CssCommand.php      |  80 +++++
 core/lib/Drupal/Core/Ajax/DataCommand.php     |  79 +++++
 core/lib/Drupal/Core/Ajax/HtmlCommand.php     |  40 +++
 core/lib/Drupal/Core/Ajax/InsertCommand.php   |  78 +++++
 core/lib/Drupal/Core/Ajax/InvokeCommand.php   |  78 +++++
 core/lib/Drupal/Core/Ajax/PrependCommand.php  |  40 +++
 core/lib/Drupal/Core/Ajax/RemoveCommand.php   |  53 +++
 core/lib/Drupal/Core/Ajax/ReplaceCommand.php  |  40 +++
 core/lib/Drupal/Core/Ajax/RestripeCommand.php |  54 +++
 core/lib/Drupal/Core/Ajax/SettingsCommand.php |  69 ++++
 core/modules/file/file.module                 |  17 +-
 .../Tests/Ajax/AjaxCommandsUnitTest.php       | 309 ++++++++++++++++++
 20 files changed, 1364 insertions(+), 9 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Ajax/AddCssCommand.php
 create mode 100644 core/lib/Drupal/Core/Ajax/AfterCommand.php
 create mode 100644 core/lib/Drupal/Core/Ajax/AjaxResponse.php
 create mode 100644 core/lib/Drupal/Core/Ajax/AlertCommand.php
 create mode 100644 core/lib/Drupal/Core/Ajax/AppendCommand.php
 create mode 100644 core/lib/Drupal/Core/Ajax/BeforeCommand.php
 create mode 100644 core/lib/Drupal/Core/Ajax/ChangedCommand.php
 create mode 100644 core/lib/Drupal/Core/Ajax/CommandInterface.php
 create mode 100644 core/lib/Drupal/Core/Ajax/CssCommand.php
 create mode 100644 core/lib/Drupal/Core/Ajax/DataCommand.php
 create mode 100644 core/lib/Drupal/Core/Ajax/HtmlCommand.php
 create mode 100644 core/lib/Drupal/Core/Ajax/InsertCommand.php
 create mode 100644 core/lib/Drupal/Core/Ajax/InvokeCommand.php
 create mode 100644 core/lib/Drupal/Core/Ajax/PrependCommand.php
 create mode 100644 core/lib/Drupal/Core/Ajax/RemoveCommand.php
 create mode 100644 core/lib/Drupal/Core/Ajax/ReplaceCommand.php
 create mode 100644 core/lib/Drupal/Core/Ajax/RestripeCommand.php
 create mode 100644 core/lib/Drupal/Core/Ajax/SettingsCommand.php
 create mode 100644 core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxCommandsUnitTest.php

diff --git a/core/lib/Drupal/Core/Ajax/AddCssCommand.php b/core/lib/Drupal/Core/Ajax/AddCssCommand.php
new file mode 100644
index 000000000000..a5dc5bd490dd
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/AddCssCommand.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\AddCssCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\CommandInterface;
+
+/**
+ * An AJAX command for adding css to the page via ajax.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.add_css()
+ * defined in misc/ajax.js.
+ *
+ * @see misc/ajax.js
+ */
+class AddCssCommand implements CommandInterface {
+
+  /**
+   * A string that contains the styles to be added to the page.
+   *
+   * It should include the wrapping style tag.
+   *
+   * @var string
+   */
+  protected $styles;
+
+  /**
+   * Constructs an AddCssCommand.
+   *
+   * @param string $styles
+   *   A string that contains the styles to be added to the page, including the
+   *   wrapping <style> tag.
+   */
+  public function __construct($styles) {
+    $this->styles = $styles;
+  }
+
+  /**
+   * Implements Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+
+    return array(
+      'command' => 'add_css',
+      'data' => $this->styles,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/AfterCommand.php b/core/lib/Drupal/Core/Ajax/AfterCommand.php
new file mode 100644
index 000000000000..096d9bc6d65b
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/AfterCommand.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\AfterCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\InsertCommand;
+
+/**
+ * An AJAX command for calling the jQuery after() method.
+ *
+ * The 'insert/after' command instructs the client to use jQuery's after()
+ * method to insert the given HTML content after each element matched by the
+ * given selector.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.insert()
+ * defined in misc/ajax.js.
+ *
+ * @see http://docs.jquery.com/Manipulation/after#content
+ */
+class AfterCommand extends InsertCommand {
+
+  /**
+   * Implements Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+
+    return array(
+      'command' => 'insert',
+      'method' => 'after',
+      'selector' => $this->selector,
+      'data' => $this->html,
+      'settings' => $this->settings,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponse.php b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
new file mode 100644
index 000000000000..12250b991eb1
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\AjaxResponse.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * JSON response object for AJAX requests.
+ */
+class AjaxResponse extends JsonResponse {
+
+  /**
+   * The array of ajax commands.
+   *
+   * @var array
+   */
+  protected $commands = array();
+
+  /**
+   * Add an AJAX command to the response.
+   *
+   * @param object $command
+   *   An AJAX command object implementing CommandInterface.
+   *
+   * @return AjaxResponse
+   *   The current AjaxResponse.
+   */
+  public function addCommand($command) {
+    $this->commands[] = $command->render();
+    return $this;
+  }
+
+  /**
+   * Sets the response's data to be the array of AJAX commands.
+   *
+   * @param
+   *   $request A request object.
+   *
+   * @return
+   *   Response The current response.
+   */
+  public function prepare(Request $request) {
+
+    parent::setData($this->ajaxRender($request));
+    return parent::prepare($request);
+  }
+
+  /**
+   * Prepares the AJAX commands for sending back to the client.
+   *
+   * @param Request
+   *   The request object that the AJAX is responding to.
+   *
+   * @return array
+   *   An array of commands ready to be returned as JSON.
+   */
+  protected function ajaxRender($request) {
+    // Ajax responses aren't rendered with html.tpl.php, so we have to call
+    // drupal_get_css() and drupal_get_js() here, in order to have new files
+    // added during this request to be loaded by the page. We only want to send
+    // back files that the page hasn't already loaded, so we implement simple
+    // diffing logic using array_diff_key().
+    foreach (array('css', 'js') as $type) {
+      // It is highly suspicious if $_POST['ajax_page_state'][$type] is empty,
+      // since the base page ought to have at least one JS file and one CSS file
+      // loaded. It probably indicates an error, and rather than making the page
+      // reload all of the files, instead we return no new files.
+      if (empty($request->parameters['ajax_page_state'][$type])) {
+        $items[$type] = array();
+      }
+      else {
+        $function = 'drupal_add_' . $type;
+        $items[$type] = $function();
+        drupal_alter($type, $items[$type]);
+        // @todo Inline CSS and JS items are indexed numerically. These can't be
+        //   reliably diffed with array_diff_key(), since the number can change
+        //   due to factors unrelated to the inline content, so for now, we
+        //   strip the inline items from Ajax responses, and can add support for
+        //   them when drupal_add_css() and drupal_add_js() are changed to using
+        //   md5() or some other hash of the inline content.
+        foreach ($items[$type] as $key => $item) {
+          if (is_numeric($key)) {
+            unset($items[$type][$key]);
+          }
+        }
+        // Ensure that the page doesn't reload what it already has.
+        $items[$type] = array_diff_key($items[$type], $request->parameters['ajax_page_state'][$type]);
+      }
+    }
+
+    // Render the HTML to load these files, and add AJAX commands to insert this
+    // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the
+    // data from being altered again, as we already altered it above. Settings
+    // are handled separately, afterwards.
+    if (isset($items['js']['settings'])) {
+      unset($items['js']['settings']);
+    }
+    $styles = drupal_get_css($items['css'], TRUE);
+    $scripts_footer = drupal_get_js('footer', $items['js'], TRUE);
+    $scripts_header = drupal_get_js('header', $items['js'], TRUE);
+
+    if (!empty($styles)) {
+      $this->addCommand(new AddCssCommand($styles));
+    }
+    if (!empty($scripts_header)) {
+      $this->addCommand(new PrependCommand('head', $scripts_header));
+    }
+    if (!empty($scripts_footer)) {
+      $this->addCommand(new AppendCommand('body', $scripts_footer));
+    }
+
+    // Now add 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(call_user_func_array('array_merge_recursive', $settings['data']), TRUE));
+    }
+
+    $commands = $this->commands;
+    drupal_alter('ajax_render', $commands);
+
+    return $commands;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/AlertCommand.php b/core/lib/Drupal/Core/Ajax/AlertCommand.php
new file mode 100644
index 000000000000..02a96a39607f
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/AlertCommand.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\AlertCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\CommandInterface;
+
+/**
+ * AJAX command for a javascript alert box.
+ */
+class AlertCommand implements CommandInterface {
+
+  /**
+   * The text to be displayed in the alert box.
+   *
+   * @var string
+   */
+  protected $text;
+
+  /**
+   * Constructs an AlertCommand object.
+   *
+   * @param string $text
+   *   The text to be displayed in the alert box.
+   */
+  public function __construct($text) {
+    $this->text = $text;
+  }
+
+  /**
+   * Implements Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+
+    return array(
+      'command' => 'alert',
+      'text' => $this->text,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/AppendCommand.php b/core/lib/Drupal/Core/Ajax/AppendCommand.php
new file mode 100644
index 000000000000..7ba98bd81158
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/AppendCommand.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\AppendCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\InsertCommand;
+
+/**
+ * An AJAX command for calling the jQuery append() method.
+ *
+ * The 'insert/append' command instructs the client to use jQuery's append()
+ * method to append the given HTML content to the inside of each element matched
+ * by the given selector.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.insert()
+ * defined in misc/ajax.js.
+ *
+ * @see http://docs.jquery.com/Manipulation/append#content
+ */
+class AppendCommand extends InsertCommand {
+
+  /**
+   * Implements Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+
+    return array(
+      'command' => 'insert',
+      'method' => 'append',
+      'selector' => $this->selector,
+      'data' => $this->html,
+      'settings' => $this->settings,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/BeforeCommand.php b/core/lib/Drupal/Core/Ajax/BeforeCommand.php
new file mode 100644
index 000000000000..0d1dc856ba40
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/BeforeCommand.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\InsertCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\InsertCommand;
+
+/**
+ * An AJAX command for calling the jQuery before() method.
+ *
+ * The 'insert/before' command instructs the client to use jQuery's before()
+ * method to insert the given HTML content before each of elements matched by
+ * the given selector.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.insert()
+ * defined in misc/ajax.js.
+ *
+ * @see http://docs.jquery.com/Manipulation/before#content
+ */
+class BeforeCommand extends InsertCommand {
+
+  /**
+   * Implements Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+
+    return array(
+      'command' => 'insert',
+      'method' => 'before',
+      'selector' => $this->selector,
+      'data' => $this->html,
+      'settings' => $this->settings,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/ChangedCommand.php b/core/lib/Drupal/Core/Ajax/ChangedCommand.php
new file mode 100644
index 000000000000..d3c7cdcd6781
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/ChangedCommand.php
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\ChangedCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\CommandInterface;
+
+/**
+ * An AJAX command for marking HTML elements as changed.
+ *
+ * This command instructs the client to mark each of the elements matched by the
+ * given selector as 'ajax-changed'.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.changed()
+ * defined in misc/ajax.js.
+ */
+class ChangedCommand implements CommandInterface {
+
+  /**
+   * A CSS selector string.
+   *
+   * If the command is a response to a request from an #ajax form element then
+   * this value can be NULL.
+   *
+   * @var string
+   */
+  protected $selector;
+
+  /**
+   * An optional CSS selector for elements to which asterisks will be appended.
+   *
+   * @var string
+   */
+  protected $asterisk;
+
+  /**
+   * Constructs a ChangedCommand object.
+   *
+   * @param string $selector
+   *   CSS selector for elements to be marked as changed.
+   * @param string $asterisk
+   *   CSS selector for elements to which an asterisk will be appended.
+   */
+  public function __construct($selector, $asterisk = '') {
+    $this->selector = $selector;
+    $this->asterisk = $asterisk;
+  }
+
+  /**
+   * Implements Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+
+    return array(
+      'command' => 'changed',
+      'selector' => $this->selector,
+      'asterisk' => $this->asterisk,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/CommandInterface.php b/core/lib/Drupal/Core/Ajax/CommandInterface.php
new file mode 100644
index 000000000000..9de3af57d14b
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/CommandInterface.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\CommandInterface.
+ */
+
+namespace Drupal\Core\Ajax;
+
+/**
+ * AJAX command interface.
+ *
+ * All AJAX commands passed to AjaxResponse objects should implement these
+ * methods.
+ */
+interface CommandInterface {
+
+  /**
+   * Return an array to be run through json_encode and sent to the client.
+   */
+  public function render();
+}
diff --git a/core/lib/Drupal/Core/Ajax/CssCommand.php b/core/lib/Drupal/Core/Ajax/CssCommand.php
new file mode 100644
index 000000000000..de55e7b8541a
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/CssCommand.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\CssCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\CommandInterface;
+
+/**
+ * An AJAX command for calling the jQuery css() method.
+ *
+ * The 'css' command will instruct the client to use the jQuery css() method to
+ * apply the CSS arguments to elements matched by the given selector.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.css() defined
+ * in misc/ajax.js.
+ *
+ * @see http://docs.jquery.com/CSS/css#properties
+ */
+class CssCommand implements CommandInterface {
+
+  /**
+   * A CSS selector string.
+   *
+   * If the command is a response to a request from an #ajax form element then
+   * this value can be NULL.
+   *
+   * @var string
+   */
+  protected $selector;
+
+  /**
+   * An array of property/value pairs to set in the CSS for the selector.
+   *
+   * @var array
+   */
+  protected $css = array();
+
+  /**
+   * Constructs a CssCommand object.
+   *
+   * @param string $selector
+   *   A CSS selector for elements to which the CSS will be applied.
+   * @param array $css
+   *   An array of CSS property/value pairs to set.
+   */
+  public function __construct($selector, array $css = array()) {
+    $this->selector = $selector;
+    $this->css = $css;
+  }
+
+  /**
+   * Adds a property/value pair to the CSS to be added to this element.
+   *
+   * @param $property
+   *   The CSS property to be changed.
+   * @param $value
+   *   The new value of the CSS property.
+   */
+  public function setProperty($property, $value) {
+    $this->css[$property] = $value;
+    return $this;
+  }
+
+  /**
+   * Implements Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+
+    return array(
+      'command' => 'css',
+      'selector' => $this->selector,
+      'argument' => $this->css,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/DataCommand.php b/core/lib/Drupal/Core/Ajax/DataCommand.php
new file mode 100644
index 000000000000..a45d4a9693ae
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/DataCommand.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\DataCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\CommandInterface;
+
+/**
+ * An AJAX command for implementing jQuery's data() method.
+ *
+ * This instructs the client to attach the name=value pair of data to the
+ * selector via jQuery's data cache.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.data() defined
+ * in misc/ajax.js.
+ */
+class DataCommand implements CommandInterface {
+
+  /**
+   * A CSS selector string for elements to which data will be attached.
+   *
+   * If the command is a response to a request from an #ajax form element then
+   * this value can be NULL.
+   *
+   * @var string
+   */
+  protected $selector;
+
+  /**
+   * The key of the data attached to elements matched by the selector.
+   *
+   * @var string
+   */
+  protected $name;
+
+  /**
+   * The value of the data to be atached to elements matched by the selector.
+   *
+   * The data is not limited to strings; it can be any format.
+   *
+   * @var mixed
+   */
+  protected $value;
+
+  /**
+   * Constructs a DataCommand object.
+   *
+   * @param string $selector
+   *   A CSS selector for the elements to which the data will be attached.
+   * @param string $name
+   *   The key of the data to be attached to elements matched by the selector.
+   * @param type $value
+   *   The value of the data to be attached to elements matched by the selector.
+   */
+  public function __construct($selector, $name, $value) {
+    $this->selector = $selector;
+    $this->name = $name;
+    $this->value = $value;
+  }
+
+  /**
+   * Implements Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+
+    return array(
+      'command' => 'data',
+      'selector' => $this->selector,
+      'name' => $this->name,
+      'value' => $this->value,
+    );
+  }
+
+}
+
diff --git a/core/lib/Drupal/Core/Ajax/HtmlCommand.php b/core/lib/Drupal/Core/Ajax/HtmlCommand.php
new file mode 100644
index 000000000000..d9bb0f6cff34
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/HtmlCommand.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\HtmlCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\InsertCommand;
+
+/**
+ * AJAX command for calling the jQuery html() method.
+ *
+ * The 'insert/html' command instructs the client to use jQuery's html() method
+ * to set the HTML content of each element matched by the given selector while
+ * leaving the outer tags intact.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.insert()
+ * defined in misc/ajax.js.
+ *
+ * @see http://docs.jquery.com/Attributes/html#val
+ */
+class HtmlCommand extends InsertCommand {
+
+  /**
+   * Implements Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+
+    return array(
+      'command' => 'insert',
+      'method' => 'html',
+      'selector' => $this->selector,
+      'data' => $this->html,
+      'settings' => $this->settings,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/InsertCommand.php b/core/lib/Drupal/Core/Ajax/InsertCommand.php
new file mode 100644
index 000000000000..8c0e93815d46
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/InsertCommand.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\InsertCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\CommandInterface;
+
+/**
+ * Generic AJAX command for inserting content.
+ *
+ * This command instructs the client to insert the given HTML using whichever
+ * jQuery DOM manipulation method has been specified in the #ajax['method']
+ * variable of the element that triggered the request.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.insert()
+ * defined in misc/ajax.js.
+ */
+class InsertCommand implements CommandInterface {
+
+  /**
+   * A CSS selector string.
+   *
+   * If the command is a response to a request from an #ajax form element then
+   * this value can be NULL.
+   *
+   * @var string
+   */
+  protected $selector;
+
+  /**
+   * The HTML content that will replace the matched element(s).
+   *
+   * @var string
+   */
+  protected $html;
+
+  /**
+   * A settings array to be passed to any any attached JavaScript behavior.
+   *
+   * @var array
+   */
+  protected $settings;
+
+  /**
+   * Constructs an InsertCommand object.
+   *
+   * @param string $selector
+   *   A CSS selector.
+   * @param string $html
+   *   String of HTML that will replace the matched element(s).
+   * @param array $settings
+   *   An array of JavaScript settings to be passed to any attached behaviors.
+   */
+  public function __construct($selector, $html, array $settings = NULL) {
+    $this->selector = $selector;
+    $this->html = $html;
+    $this->settings = $settings;
+  }
+
+  /**
+   * Implements Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+
+    return array(
+      'command' => 'insert',
+      'method' => NULL,
+      'selector' => $this->selector,
+      'data' => $this->html,
+      'settings' => $this->settings,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/InvokeCommand.php b/core/lib/Drupal/Core/Ajax/InvokeCommand.php
new file mode 100644
index 000000000000..ad5e96595438
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/InvokeCommand.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\InvokeCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\CommandInterface;
+
+/**
+ * AJAX command for invoking an arbitrary jQuery method.
+ *
+ * The 'invoke' command will instruct the client to invoke the given jQuery
+ * method with the supplied arguments on the elements matched by the given
+ * selector. Intended for simple jQuery commands, such as attr(), addClass(),
+ * removeClass(), toggleClass(), etc.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.invoke()
+ * defined in misc/ajax.js.
+ */
+class InvokeCommand implements CommandInterface {
+
+  /**
+   * A CSS selector string.
+   *
+   * If the command is a response to a request from an #ajax form element then
+   * this value can be NULL.
+   *
+   * @var string
+   */
+  protected $selector;
+
+  /**
+   * A jQuery method to invoke.
+   *
+   * @var string
+   */
+  protected $method;
+
+  /**
+   * An optional list of arguments to pass to the method.
+   *
+   * @var array
+   */
+  protected $arguments;
+
+  /**
+   * Constructs an InvokeCommand object.
+   *
+   * @param string $selector
+   *   A jQuery selector.
+   * @param string $method
+   *   The name of a jQuery method to invoke.
+   * @param array $arguments
+   *   An optional array of arguments to pass to the method.
+   */
+  public function __construct($selector, $method, array $arguments = array()) {
+    $this->selector = $selector;
+    $this->method = $method;
+    $this->arguments = $arguments;
+  }
+
+  /**
+   * Implements Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+
+    return array(
+      'command' => 'invoke',
+      'selector' => $this->selector,
+      'method' => $this->method,
+      'args' => $this->arguments,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/PrependCommand.php b/core/lib/Drupal/Core/Ajax/PrependCommand.php
new file mode 100644
index 000000000000..5557c31a014c
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/PrependCommand.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\PrependCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\InsertCommand;
+
+/**
+ * AJAX command for calling the jQuery insert() method.
+ *
+ * The 'insert/prepend' command instructs the client to use jQuery's prepend()
+ * method to prepend the given HTML content to the inside each element matched
+ * by the given selector.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.insert()
+ * defined in misc/ajax.js.
+ *
+ * @see http://docs.jquery.com/Manipulation/prepend#content
+ */
+class PrependCommand extends InsertCommand {
+
+  /**
+   * Implements Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+
+    return array(
+      'command' => 'insert',
+      'method' => 'prepend',
+      'selector' => $this->selector,
+      'data' => $this->html,
+      'settings' => $this->settings,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/RemoveCommand.php b/core/lib/Drupal/Core/Ajax/RemoveCommand.php
new file mode 100644
index 000000000000..b77db99f2af4
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/RemoveCommand.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\RemoveCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\CommandInterface;
+
+/**
+ * AJAX command for calling the jQuery remove() method.
+ *
+ * The 'remove' command instructs the client to use jQuery's remove() method
+ * to remove each of elements matched by the given selector, and everything
+ * within them.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.remove()
+ * defined in misc/ajax.js.
+ *
+ * @see http://docs.jquery.com/Manipulation/remove#expr
+ */
+class RemoveCommand Implements CommandInterface {
+
+  /**
+   * The CSS selector for the element(s) to be removed.
+   *
+   * @var string
+   */
+  protected $selector;
+
+  /**
+   * Constructs a RemoveCommand object.
+   *
+   * @param string $selector
+   *
+   */
+  public function __construct($selector) {
+    $this->selector = $selector;
+  }
+
+  /**
+   * Implements Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+    return array(
+      'command' => 'remove',
+      'selector' => $this->selector,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/ReplaceCommand.php b/core/lib/Drupal/Core/Ajax/ReplaceCommand.php
new file mode 100644
index 000000000000..01c7667dcbbc
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/ReplaceCommand.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\ReplaceCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\InsertCommand;
+
+/**
+ * AJAX command for calling the jQuery replace() method.
+ *
+ * The 'insert/replaceWith' command instructs the client to use jQuery's
+ * replaceWith() method to replace each element matched matched by the given
+ * selector with the given HTML.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.insert()
+ * defined in misc/ajax.js.
+ *
+ * See @link http://docs.jquery.com/Manipulation/replaceWith#content jQuery replaceWith command @endlink
+ */
+class ReplaceCommand extends InsertCommand {
+
+  /**
+   * Implements Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+
+    return array(
+      'command' => 'insert',
+      'method' => 'replaceWith',
+      'selector' => $this->selector,
+      'data' => $this->html,
+      'settings' => $this->settings,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/RestripeCommand.php b/core/lib/Drupal/Core/Ajax/RestripeCommand.php
new file mode 100644
index 000000000000..93c61aad7d6c
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/RestripeCommand.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\RestripeCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\CommandInterface;
+
+/**
+ * AJAX command for resetting the striping on a table.
+ *
+ * The 'restripe' command instructs the client to restripe a table. This is
+ * usually used after a table has been modified by a replace or append command.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.restripe()
+ * defined in misc/ajax.js.
+ */
+class RestripeCommand implements CommandInterface {
+
+  /**
+   * A CSS selector string.
+   *
+   * If the command is a response to a request from an #ajax form element then
+   * this value can be NULL.
+   *
+   * @var string
+   */
+  protected $selector;
+
+  /**
+   * Constructs a RestripeCommand object.
+   *
+   * @param string $selector
+   *   A CSS selector for the table to be restriped.
+   */
+  public function __construct($selector) {
+    $this->selector = $selector;
+  }
+
+  /**
+   * Implements Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+
+    return array(
+      'command' => 'restripe',
+      'selector' => $this->selector,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Ajax/SettingsCommand.php b/core/lib/Drupal/Core/Ajax/SettingsCommand.php
new file mode 100644
index 000000000000..7a00f8ac4ea9
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/SettingsCommand.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Ajax\SettingsCommand.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Ajax\CommandInterface;
+
+/**
+ * AJAX command for adjusting Drupal's JavaScript settings.
+ *
+ * The 'settings' command instructs the client either to use the given array as
+ * the settings for ajax-loaded content or to extend Drupal.settings with the
+ * given array, depending on the value of the $merge parameter.
+ *
+ * This command is implemented by Drupal.ajax.prototype.commands.settings()
+ * defined in misc/ajax.js.
+ */
+class SettingsCommand implements CommandInterface {
+
+  /**
+   * An array of key/value pairs of JavaScript settings.
+   *
+   * This will be utilized for all commands after this if they do not include
+   * their own settings array.
+   *
+   * @var array
+   */
+  protected $settings;
+
+  /**
+   * Whether the settings should be merged into the global Drupal.settings.
+   *
+   * By default (FALSE), the settings that are passed to Drupal.attachBehaviors
+   * will not include the global Drupal.settings.
+   *
+   * @var boolean
+   */
+  protected $merge;
+
+  /**
+   * Constructs a SettingsCommand object.
+   *
+   * @param array $settings
+   *   An array of key/value pairs of JavaScript settings.
+   * @param boolean $merge
+   *   Whether the settings should be merged into the global Drupal.settings.
+   */
+  public function __construct(array $settings, $merge = FALSE) {
+    $this->settings = $settings;
+    $this->merge = $merge;
+  }
+
+  /**
+   * Implements Drupal\Core\Ajax\CommandInterface:render().
+   */
+  public function render() {
+
+    return array(
+      'command' => 'settings',
+      'settings' => $this->settings,
+      'merge' => $this->merge,
+    );
+  }
+
+}
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 6694598593f3..445e7ab69a80 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -10,6 +10,8 @@
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Drupal\file\FileUsage\DatabaseFileUsageBackend;
 use Drupal\file\FileUsage\FileUsageInterface;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\ReplaceCommand;
 
 // Load all Field module hooks for File.
 require_once DRUPAL_ROOT . '/core/modules/file/file.field.inc';
@@ -750,9 +752,8 @@ function file_ajax_upload() {
   if (empty($_POST['form_build_id']) || $form_build_id != $_POST['form_build_id']) {
     // Invalid request.
     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);
+    $response = new AjaxResponse();
+    return $response->addCommand(new ReplaceCommand(NULL, theme('status_messages')));
   }
 
   list($form, $form_state) = ajax_get_form();
@@ -760,9 +761,8 @@ function file_ajax_upload() {
   if (!$form) {
     // Invalid form_build_id.
     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);
+    $response = new AjaxResponse();
+    return $response->addCommand(new ReplaceCommand(NULL, theme('status_messages')));
   }
 
   // Get the current element and count the number of files.
@@ -793,9 +793,8 @@ function file_ajax_upload() {
   $js = drupal_add_js();
   $settings = call_user_func_array('array_merge_recursive', $js['settings']['data']);
 
-  $commands = array();
-  $commands[] = ajax_command_replace(NULL, $output, $settings);
-  return array('#type' => 'ajax', '#commands' => $commands);
+  $response = new AjaxResponse();
+  return $response->addCommand(new ReplaceCommand(NULL, $output, $settings));
 }
 
 /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxCommandsUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxCommandsUnitTest.php
new file mode 100644
index 000000000000..2e6c421f5972
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Ajax/AjaxCommandsUnitTest.php
@@ -0,0 +1,309 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Ajax\AjaxCommandsUnitTest.
+ */
+
+namespace Drupal\system\Tests\Ajax;
+
+use Drupal\simpletest\UnitTestBase;
+use Drupal\Core\Ajax\AddCssCommand;
+use Drupal\Core\Ajax\AfterCommand;
+use Drupal\Core\Ajax\AlertCommand;
+use Drupal\Core\Ajax\AppendCommand;
+use Drupal\Core\Ajax\BeforeCommand;
+use Drupal\Core\Ajax\ChangedCommand;
+use Drupal\Core\Ajax\CssCommand;
+use Drupal\Core\Ajax\DataCommand;
+use Drupal\Core\Ajax\HtmlCommand;
+use Drupal\Core\Ajax\InsertCommand;
+use Drupal\Core\Ajax\InvokeCommand;
+use Drupal\Core\Ajax\PrependCommand;
+use Drupal\Core\Ajax\RemoveCommand;
+use Drupal\Core\Ajax\ReplaceCommand;
+use Drupal\Core\Ajax\RestripeCommand;
+use Drupal\Core\Ajax\SettingsCommand;
+
+/**
+ * Tests for all AJAX Commands.
+ */
+class AjaxCommandsUnitTest extends UnitTestBase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Ajax Command Objects',
+      'description' => 'Test that each AJAX command object can be created and rendered',
+      'group' => 'AJAX',
+    );
+  }
+
+  /**
+   * Tests that AddCssCommand objects can be constructed and rendered.
+   */
+  function testAddCssCommand() {
+
+    $command = new AddCssCommand('p{ text-decoration:blink; }');
+
+    $expected = array(
+      'command' => 'add_css',
+      'data' => 'p{ text-decoration:blink; }',
+    );
+
+    $this->assertEqual($command->render(), $expected, 'AddCssCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that AfterCommand objecst can be constructed and rendered.
+   */
+  function testAfterCommand() {
+
+    $command = new AfterCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting'));
+
+    $expected = array(
+      'command' => 'insert',
+      'method' => 'after',
+      'selector' => '#page-title',
+      'data' => '<p>New Text!</p>',
+      'settings' => array('my-setting' => 'setting'),
+    );
+
+    $this->assertEqual($command->render(), $expected, 'AfterCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that AlertCommand objects can be constructed and rendered.
+   */
+  function testAlertCommand() {
+    $command = new AlertCommand('Set condition 1 throughout the ship!');
+    $expected = array(
+      'command' => 'alert',
+      'text' => 'Set condition 1 throughout the ship!',
+    );
+
+    $this->assertEqual($command->render(), $expected, 'AlertCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that AppendCommand objects can be constructed and rendered.
+   */
+  function testAppendCommand() {
+    // Test AppendCommand.
+    $command = new AppendCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting'));
+
+    $expected = array(
+      'command' => 'insert',
+      'method' => 'append',
+      'selector' => '#page-title',
+      'data' => '<p>New Text!</p>',
+      'settings' => array('my-setting' => 'setting'),
+    );
+
+    $this->assertEqual($command->render(), $expected, 'AppendCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that BeforeCommand objects can be constructed and rendered.
+   */
+  function testBeforeCommand() {
+
+    $command = new BeforeCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting'));
+
+    $expected = array(
+      'command' => 'insert',
+      'method' => 'before',
+      'selector' => '#page-title',
+      'data' => '<p>New Text!</p>',
+      'settings' => array('my-setting' => 'setting'),
+    );
+
+    $this->assertEqual($command->render(), $expected, 'BeforeCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that ChangedCommand objects can be constructed and rendered.
+   */
+  function testChangedCommand() {
+    $command = new ChangedCommand('#page-title', '#page-title-changed');
+
+    $expected = array(
+      'command' => 'changed',
+      'selector' => '#page-title',
+      'asterisk' => '#page-title-changed',
+    );
+
+    $this->assertEqual($command->render(), $expected, 'ChangedCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that CssCommand objects can be constructed and rendered.
+   */
+  function testCssCommand() {
+
+    $command = new CssCommand('#page-title', array('text-decoration' => 'blink'));
+    $command->setProperty('font-size', '40px')->setProperty('font-weight', 'bold');
+
+    $expected = array(
+      'command' => 'css',
+      'selector' => '#page-title',
+      'argument' => array(
+        'text-decoration' => 'blink',
+        'font-size' => '40px',
+        'font-weight' => 'bold',
+      ),
+    );
+
+    $this->assertEqual($command->render(), $expected, 'CssCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that DataCommand objects can be constructed and rendered.
+   */
+  function testDataCommand() {
+
+    $command = new DataCommand('#page-title', 'my-data', array('key' => 'value'));
+
+    $expected = array(
+      'command' => 'data',
+      'selector' => '#page-title',
+      'name' => 'my-data',
+      'value' => array('key' => 'value'),
+    );
+
+    $this->assertEqual($command->render(), $expected, 'DataCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that HtmlCommand objects can be constructed and rendered.
+   */
+  function testHtmlCommand() {
+
+    $command = new HtmlCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting'));
+
+    $expected = array(
+      'command' => 'insert',
+      'method' => 'html',
+      'selector' => '#page-title',
+      'data' => '<p>New Text!</p>',
+      'settings' => array('my-setting' => 'setting'),
+    );
+
+    $this->assertEqual($command->render(), $expected, 'HtmlCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that InsertCommand objects can be constructed and rendered.
+   */
+  function testInsertCommand() {
+
+    $command = new InsertCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting'));
+
+    $expected = array(
+      'command' => 'insert',
+      'method' => NULL,
+      'selector' => '#page-title',
+      'data' => '<p>New Text!</p>',
+      'settings' => array('my-setting' => 'setting'),
+    );
+
+    $this->assertEqual($command->render(), $expected, 'InsertCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that InvokeCommand objects can be constructed and rendered.
+   */
+  function testInvokeCommand() {
+
+    $command = new InvokeCommand('#page-title', 'myMethod', array('var1', 'var2'));
+
+    $expected = array(
+      'command' => 'invoke',
+      'selector' => '#page-title',
+      'method' => 'myMethod',
+      'args' => array('var1', 'var2'),
+    );
+
+    $this->assertEqual($command->render(), $expected, 'InvokeCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that PrependCommand objects can be constructed and rendered.
+   */
+  function testPrependCommand() {
+
+    $command = new PrependCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting'));
+
+    $expected = array(
+      'command' => 'insert',
+      'method' => 'prepend',
+      'selector' => '#page-title',
+      'data' => '<p>New Text!</p>',
+      'settings' => array('my-setting' => 'setting'),
+    );
+
+    $this->assertEqual($command->render(), $expected, 'PrependCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that RemoveCommand objects can be constructed and rendered.
+   */
+  function testRemoveCommand() {
+
+    $command = new RemoveCommand('#page-title');
+
+    $expected = array(
+      'command' => 'remove',
+      'selector' => '#page-title',
+    );
+
+    $this->assertEqual($command->render(), $expected, 'RemoveCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that ReplaceCommand objects can be constructed and rendered.
+   */
+  function testReplaceCommand() {
+    $command = new ReplaceCommand('#page-title', '<p>New Text!</p>', array('my-setting' => 'setting'));
+
+    $expected = array(
+      'command' => 'insert',
+      'method' => 'replaceWith',
+      'selector' => '#page-title',
+      'data' => '<p>New Text!</p>',
+      'settings' => array('my-setting' => 'setting'),
+    );
+
+    $this->assertEqual($command->render(), $expected, 'ReplaceCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that RestripeCommand objects can be constructed and rendered.
+   */
+  function testRestripeCommand() {
+    $command = new RestripeCommand('#page-title');
+
+    $expected = array(
+      'command' => 'restripe',
+      'selector' => '#page-title',
+    );
+
+    $this->assertEqual($command->render(), $expected, 'RestripeCommand::render() returns a proper array.');
+  }
+
+  /**
+   * Tests that SettingsCommand objects can be constructed and rendered.
+   */
+  function testSettingsCommand() {
+    $command = new SettingsCommand(array('key' => 'value'), TRUE);
+
+    $expected = array(
+      'command' => 'settings',
+      'settings' => array('key' => 'value'),
+      'merge' => TRUE,
+    );
+
+    $this->assertEqual($command->render(), $expected, 'SettingsCommand::render() returns a proper array.');
+  }
+
+}
+
-- 
GitLab