summaryrefslogtreecommitdiffstats
path: root/core/lib/Drupal/Core/Ajax/AjaxResponse.php
blob: 256ea955817fb3b80aa1b744aac12a1cb057d9c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
<?php

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

namespace Drupal\Core\Ajax;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * 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 \Drupal\Core\Ajax\CommandInterface $command
   *   An AJAX command object implementing CommandInterface.
   * @param boolean $prepend
   *   A boolean which determines whether the new command should be executed
   *   before previously added commands. Defaults to FALSE.
   *
   * @return AjaxResponse
   *   The current AjaxResponse.
   */
  public function addCommand(CommandInterface $command, $prepend = FALSE) {
    if ($prepend) {
      array_unshift($this->commands, $command->render());
    }
    else {
      $this->commands[] = $command->render();
    }

    return $this;
  }

  /**
   * Gets all AJAX commands.
   *
   * @return \Drupal\Core\Ajax\CommandInterface[]
   *   Returns all previously added AJAX commands.
   */
  public function &getCommands() {
    return $this->commands;
  }

  /**
   * {@inheritdoc}
   *
   * Sets the response's data to be the array of AJAX commands.
   */
  public function prepare(Request $request) {
    $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));
    }
  }

  /**
   * Prepares the AJAX commands for sending back to the client.
   *
   * @param Request $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 $request) {
    // Ajax responses aren't rendered with html.html.twig, 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().
    $ajax_page_state = $request->request->get('ajax_page_state');
    foreach (array('css', 'js') as $type) {
      // 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.
      if (empty($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 use
        //   a hash of the inline content as the array key.
        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], $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, TRUE);
    $scripts_header = drupal_get_js('header', $items['js'], TRUE, TRUE);

    // Prepend commands to add the resources, preserving their relative order.
    $resource_commands = array();
    if (!empty($styles)) {
      $resource_commands[] = new AddCssCommand($styles);
    }
    if (!empty($scripts_header)) {
      $resource_commands[] = new PrependCommand('head', $scripts_header);
    }
    if (!empty($scripts_footer)) {
      $resource_commands[] = new AppendCommand('body', $scripts_footer);
    }
    foreach (array_reverse($resource_commands) as $resource_command) {
      $this->addCommand($resource_command, TRUE);
    }

    // Prepend a command to merge changes and additions to drupalSettings.
    $scripts = drupal_add_js();
    if (!empty($scripts['settings'])) {
      $settings = drupal_merge_js_settings($scripts['settings']['data']);
      // 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]);
      }
      $this->addCommand(new SettingsCommand($settings, TRUE), TRUE);
    }

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

    return $commands;
  }

}