AjaxResponse.php 5.83 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
<?php

/**
 * @file
 * Definition of Drupal\Core\Ajax\AjaxResponse.
 */

namespace Drupal\Core\Ajax;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
12
use Symfony\Component\HttpFoundation\Response;
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

/**
 * 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.
   *
29
   * @param \Drupal\Core\Ajax\CommandInterface $command
30
   *   An AJAX command object implementing CommandInterface.
31 32 33
   * @param boolean $prepend
   *   A boolean which determines whether the new command should be executed
   *   before previously added commands. Defaults to FALSE.
34 35 36 37
   *
   * @return AjaxResponse
   *   The current AjaxResponse.
   */
38
  public function addCommand(CommandInterface $command, $prepend = FALSE) {
39 40 41 42 43 44 45
    if ($prepend) {
      array_unshift($this->commands, $command->render());
    }
    else {
      $this->commands[] = $command->render();
    }

46 47 48
    return $this;
  }

49 50 51 52 53 54 55 56 57 58
  /**
   * Gets all AJAX commands.
   *
   * @return \Drupal\Core\Ajax\CommandInterface[]
   *   Returns all previously added AJAX commands.
   */
  public function &getCommands() {
    return $this->commands;
  }

59
  /**
60
   * {@inheritdoc}
61
   *
62
   * Sets the response's data to be the array of AJAX commands.
63 64
   */
  public function prepare(Request $request) {
65 66 67 68 69 70 71 72 73 74 75 76 77 78
    $this->prepareResponse($request);
    return $this;
  }

  /**
   * Sets the rendered AJAX right before the response is prepared.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request object.
   */
  public function prepareResponse(Request $request) {
    if ($this->data == '{}') {
      $this->setData($this->ajaxRender($request));
    }
79 80 81 82 83
  }

  /**
   * Prepares the AJAX commands for sending back to the client.
   *
84
   * @param Request $request
85 86 87 88 89
   *   The request object that the AJAX is responding to.
   *
   * @return array
   *   An array of commands ready to be returned as JSON.
   */
90
  protected function ajaxRender(Request $request) {
91
    // Ajax responses aren't rendered with html.html.twig, so we have to call
92 93 94 95
    // 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().
96
    $ajax_page_state = $request->request->get('ajax_page_state');
97
    foreach (array('css', 'js') as $type) {
98 99 100 101 102
      // It is highly suspicious if
      // $request->request->get("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.
103
      if (empty($ajax_page_state[$type])) {
104 105 106 107 108 109 110 111 112 113
        $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
114 115
        //   them when drupal_add_css() and drupal_add_js() are changed to use
        //   a hash of the inline content as the array key.
116 117 118 119 120 121
        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.
122
        $items[$type] = array_diff_key($items[$type], $ajax_page_state[$type]);
123 124 125 126 127 128 129 130 131 132 133
      }
    }

    // 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);
134 135
    $scripts_footer = drupal_get_js('footer', $items['js'], TRUE, TRUE);
    $scripts_header = drupal_get_js('header', $items['js'], TRUE, TRUE);
136

137 138
    // Prepend commands to add the resources, preserving their relative order.
    $resource_commands = array();
139
    if (!empty($styles)) {
140
      $resource_commands[] = new AddCssCommand($styles);
141 142
    }
    if (!empty($scripts_header)) {
143
      $resource_commands[] = new PrependCommand('head', $scripts_header);
144 145
    }
    if (!empty($scripts_footer)) {
146 147 148 149
      $resource_commands[] = new AppendCommand('body', $scripts_footer);
    }
    foreach (array_reverse($resource_commands) as $resource_command) {
      $this->addCommand($resource_command, TRUE);
150 151
    }

152
    // Prepend a command to merge changes and additions to drupalSettings.
153 154
    $scripts = drupal_add_js();
    if (!empty($scripts['settings'])) {
155
      $settings = drupal_merge_js_settings($scripts['settings']['data']);
156 157 158 159 160 161 162 163 164
      // During Ajax requests basic path-specific settings are excluded from
      // new drupalSettings values. The original page where this request comes
      // from already has the right values for the keys below. An Ajax request
      // would update them with values for the Ajax request and incorrectly
      // override the page's values.
      // @see drupal_add_js
      foreach (array('basePath', 'currentPath', 'scriptPath', 'pathPrefix') as $item) {
        unset($settings[$item]);
      }
165
      $this->addCommand(new SettingsCommand($settings, TRUE), TRUE);
166 167 168 169 170 171 172 173 174
    }

    $commands = $this->commands;
    drupal_alter('ajax_render', $commands);

    return $commands;
  }

}