3 namespace Drupal\Core\Ajax;
5 use Drupal\Core\Asset\AssetCollectionRendererInterface;
6 use Drupal\Core\Asset\AssetResolverInterface;
7 use Drupal\Core\Asset\AttachedAssets;
8 use Drupal\Core\Config\ConfigFactoryInterface;
9 use Drupal\Core\Extension\ModuleHandlerInterface;
10 use Drupal\Core\Render\AttachmentsInterface;
11 use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
12 use Drupal\Core\Render\RendererInterface;
13 use Symfony\Component\HttpFoundation\Request;
14 use Symfony\Component\HttpFoundation\RequestStack;
17 * Processes attachments of AJAX responses.
19 * @see \Drupal\Core\Ajax\AjaxResponse
20 * @see \Drupal\Core\Render\MainContent\AjaxRenderer
22 class AjaxResponseAttachmentsProcessor implements AttachmentsResponseProcessorInterface {
25 * The asset resolver service.
27 * @var \Drupal\Core\Asset\AssetResolverInterface
29 protected $assetResolver;
32 * A config object for the system performance configuration.
34 * @var \Drupal\Core\Config\Config
39 * The CSS asset collection renderer service.
41 * @var \Drupal\Core\Asset\AssetCollectionRendererInterface
43 protected $cssCollectionRenderer;
46 * The JS asset collection renderer service.
48 * @var \Drupal\Core\Asset\AssetCollectionRendererInterface
50 protected $jsCollectionRenderer;
55 * @var \Symfony\Component\HttpFoundation\RequestStack
57 protected $requestStack;
62 * @var \Drupal\Core\Render\RendererInterface
69 * @var \Drupal\Core\Extension\ModuleHandlerInterface
71 protected $moduleHandler;
74 * Constructs a AjaxResponseAttachmentsProcessor object.
76 * @param \Drupal\Core\Asset\AssetResolverInterface $asset_resolver
78 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
79 * A config factory for retrieving required config objects.
80 * @param \Drupal\Core\Asset\AssetCollectionRendererInterface $css_collection_renderer
81 * The CSS asset collection renderer.
82 * @param \Drupal\Core\Asset\AssetCollectionRendererInterface $js_collection_renderer
83 * The JS asset collection renderer.
84 * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
86 * @param \Drupal\Core\Render\RendererInterface $renderer
88 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
91 public function __construct(AssetResolverInterface $asset_resolver, ConfigFactoryInterface $config_factory, AssetCollectionRendererInterface $css_collection_renderer, AssetCollectionRendererInterface $js_collection_renderer, RequestStack $request_stack, RendererInterface $renderer, ModuleHandlerInterface $module_handler) {
92 $this->assetResolver = $asset_resolver;
93 $this->config = $config_factory->get('system.performance');
94 $this->cssCollectionRenderer = $css_collection_renderer;
95 $this->jsCollectionRenderer = $js_collection_renderer;
96 $this->requestStack = $request_stack;
97 $this->renderer = $renderer;
98 $this->moduleHandler = $module_handler;
104 public function processAttachments(AttachmentsInterface $response) {
105 // @todo Convert to assertion once https://www.drupal.org/node/2408013 lands
106 if (!$response instanceof AjaxResponse) {
107 throw new \InvalidArgumentException('\Drupal\Core\Ajax\AjaxResponse instance expected.');
110 $request = $this->requestStack->getCurrentRequest();
112 if ($response->getContent() == '{}') {
113 $response->setData($this->buildAttachmentsCommands($response, $request));
120 * Prepares the AJAX commands to attach assets.
122 * @param \Drupal\Core\Ajax\AjaxResponse $response
123 * The AJAX response to update.
124 * @param \Symfony\Component\HttpFoundation\Request $request
125 * The request object that the AJAX is responding to.
128 * An array of commands ready to be returned as JSON.
130 protected function buildAttachmentsCommands(AjaxResponse $response, Request $request) {
131 $ajax_page_state = $request->request->get('ajax_page_state');
133 // Aggregate CSS/JS if necessary, but only during normal site operation.
134 $optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess');
135 $optimize_js = !defined('MAINTENANCE_MODE') && $this->config->get('js.preprocess');
137 $attachments = $response->getAttachments();
139 // Resolve the attached libraries into asset collections.
140 $assets = new AttachedAssets();
141 $assets->setLibraries(isset($attachments['library']) ? $attachments['library'] : [])
142 ->setAlreadyLoadedLibraries(isset($ajax_page_state['libraries']) ? explode(',', $ajax_page_state['libraries']) : [])
143 ->setSettings(isset($attachments['drupalSettings']) ? $attachments['drupalSettings'] : []);
144 $css_assets = $this->assetResolver->getCssAssets($assets, $optimize_css);
145 list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, $optimize_js);
147 // First, AttachedAssets::setLibraries() ensures duplicate libraries are
148 // removed: it converts it to a set of libraries if necessary. Second,
149 // AssetResolver::getJsSettings() ensures $assets contains the final set of
150 // JavaScript settings. AttachmentsResponseProcessorInterface also mandates
151 // that the response it processes contains the final attachment values, so
152 // update both the 'library' and 'drupalSettings' attachments accordingly.
153 $attachments['library'] = $assets->getLibraries();
154 $attachments['drupalSettings'] = $assets->getSettings();
155 $response->setAttachments($attachments);
157 // Render the HTML to load these files, and add AJAX commands to insert this
158 // HTML in the page. Settings are handled separately, afterwards.
160 if (isset($js_assets_header['drupalSettings'])) {
161 $settings = $js_assets_header['drupalSettings']['data'];
162 unset($js_assets_header['drupalSettings']);
164 if (isset($js_assets_footer['drupalSettings'])) {
165 $settings = $js_assets_footer['drupalSettings']['data'];
166 unset($js_assets_footer['drupalSettings']);
169 // Prepend commands to add the assets, preserving their relative order.
170 $resource_commands = [];
172 $css_render_array = $this->cssCollectionRenderer->render($css_assets);
173 $resource_commands[] = new AddCssCommand($this->renderer->renderPlain($css_render_array));
175 if ($js_assets_header) {
176 $js_header_render_array = $this->jsCollectionRenderer->render($js_assets_header);
177 $resource_commands[] = new PrependCommand('head', $this->renderer->renderPlain($js_header_render_array));
179 if ($js_assets_footer) {
180 $js_footer_render_array = $this->jsCollectionRenderer->render($js_assets_footer);
181 $resource_commands[] = new AppendCommand('body', $this->renderer->renderPlain($js_footer_render_array));
183 foreach (array_reverse($resource_commands) as $resource_command) {
184 $response->addCommand($resource_command, TRUE);
187 // Prepend a command to merge changes and additions to drupalSettings.
188 if (!empty($settings)) {
189 // During Ajax requests basic path-specific settings are excluded from
190 // new drupalSettings values. The original page where this request comes
191 // from already has the right values. An Ajax request would update them
192 // with values for the Ajax request and incorrectly override the page's
194 // @see system_js_settings_alter()
195 unset($settings['path']);
196 $response->addCommand(new SettingsCommand($settings, TRUE), TRUE);
199 $commands = $response->getCommands();
200 $this->moduleHandler->alter('ajax_render', $commands);