AjaxResponseAttachmentsProcessor.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. <?php
  2. namespace Drupal\Core\Ajax;
  3. use Drupal\Core\Asset\AssetCollectionRendererInterface;
  4. use Drupal\Core\Asset\AssetResolverInterface;
  5. use Drupal\Core\Asset\AttachedAssets;
  6. use Drupal\Core\Config\ConfigFactoryInterface;
  7. use Drupal\Core\Extension\ModuleHandlerInterface;
  8. use Drupal\Core\Render\AttachmentsInterface;
  9. use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
  10. use Drupal\Core\Render\RendererInterface;
  11. use Symfony\Component\HttpFoundation\Request;
  12. use Symfony\Component\HttpFoundation\RequestStack;
  13. /**
  14. * Processes attachments of AJAX responses.
  15. *
  16. * @see \Drupal\Core\Ajax\AjaxResponse
  17. * @see \Drupal\Core\Render\MainContent\AjaxRenderer
  18. */
  19. class AjaxResponseAttachmentsProcessor implements AttachmentsResponseProcessorInterface {
  20. /**
  21. * The asset resolver service.
  22. *
  23. * @var \Drupal\Core\Asset\AssetResolverInterface
  24. */
  25. protected $assetResolver;
  26. /**
  27. * A config object for the system performance configuration.
  28. *
  29. * @var \Drupal\Core\Config\Config
  30. */
  31. protected $config;
  32. /**
  33. * The CSS asset collection renderer service.
  34. *
  35. * @var \Drupal\Core\Asset\AssetCollectionRendererInterface
  36. */
  37. protected $cssCollectionRenderer;
  38. /**
  39. * The JS asset collection renderer service.
  40. *
  41. * @var \Drupal\Core\Asset\AssetCollectionRendererInterface
  42. */
  43. protected $jsCollectionRenderer;
  44. /**
  45. * The request stack.
  46. *
  47. * @var \Symfony\Component\HttpFoundation\RequestStack
  48. */
  49. protected $requestStack;
  50. /**
  51. * The renderer.
  52. *
  53. * @var \Drupal\Core\Render\RendererInterface
  54. */
  55. protected $renderer;
  56. /**
  57. * The module handler.
  58. *
  59. * @var \Drupal\Core\Extension\ModuleHandlerInterface
  60. */
  61. protected $moduleHandler;
  62. /**
  63. * Constructs a AjaxResponseAttachmentsProcessor object.
  64. *
  65. * @param \Drupal\Core\Asset\AssetResolverInterface $asset_resolver
  66. * An asset resolver.
  67. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
  68. * A config factory for retrieving required config objects.
  69. * @param \Drupal\Core\Asset\AssetCollectionRendererInterface $css_collection_renderer
  70. * The CSS asset collection renderer.
  71. * @param \Drupal\Core\Asset\AssetCollectionRendererInterface $js_collection_renderer
  72. * The JS asset collection renderer.
  73. * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
  74. * The request stack.
  75. * @param \Drupal\Core\Render\RendererInterface $renderer
  76. * The renderer.
  77. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  78. * The module handler.
  79. */
  80. 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) {
  81. $this->assetResolver = $asset_resolver;
  82. $this->config = $config_factory->get('system.performance');
  83. $this->cssCollectionRenderer = $css_collection_renderer;
  84. $this->jsCollectionRenderer = $js_collection_renderer;
  85. $this->requestStack = $request_stack;
  86. $this->renderer = $renderer;
  87. $this->moduleHandler = $module_handler;
  88. }
  89. /**
  90. * {@inheritdoc}
  91. */
  92. public function processAttachments(AttachmentsInterface $response) {
  93. // @todo Convert to assertion once https://www.drupal.org/node/2408013 lands
  94. if (!$response instanceof AjaxResponse) {
  95. throw new \InvalidArgumentException('\Drupal\Core\Ajax\AjaxResponse instance expected.');
  96. }
  97. $request = $this->requestStack->getCurrentRequest();
  98. if ($response->getContent() == '{}') {
  99. $response->setData($this->buildAttachmentsCommands($response, $request));
  100. }
  101. return $response;
  102. }
  103. /**
  104. * Prepares the AJAX commands to attach assets.
  105. *
  106. * @param \Drupal\Core\Ajax\AjaxResponse $response
  107. * The AJAX response to update.
  108. * @param \Symfony\Component\HttpFoundation\Request $request
  109. * The request object that the AJAX is responding to.
  110. *
  111. * @return array
  112. * An array of commands ready to be returned as JSON.
  113. */
  114. protected function buildAttachmentsCommands(AjaxResponse $response, Request $request) {
  115. $ajax_page_state = $request->request->get('ajax_page_state');
  116. // Aggregate CSS/JS if necessary, but only during normal site operation.
  117. $optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess');
  118. $optimize_js = !defined('MAINTENANCE_MODE') && $this->config->get('js.preprocess');
  119. $attachments = $response->getAttachments();
  120. // Resolve the attached libraries into asset collections.
  121. $assets = new AttachedAssets();
  122. $assets->setLibraries(isset($attachments['library']) ? $attachments['library'] : [])
  123. ->setAlreadyLoadedLibraries(isset($ajax_page_state['libraries']) ? explode(',', $ajax_page_state['libraries']) : [])
  124. ->setSettings(isset($attachments['drupalSettings']) ? $attachments['drupalSettings'] : []);
  125. $css_assets = $this->assetResolver->getCssAssets($assets, $optimize_css);
  126. list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, $optimize_js);
  127. // First, AttachedAssets::setLibraries() ensures duplicate libraries are
  128. // removed: it converts it to a set of libraries if necessary. Second,
  129. // AssetResolver::getJsSettings() ensures $assets contains the final set of
  130. // JavaScript settings. AttachmentsResponseProcessorInterface also mandates
  131. // that the response it processes contains the final attachment values, so
  132. // update both the 'library' and 'drupalSettings' attachments accordingly.
  133. $attachments['library'] = $assets->getLibraries();
  134. $attachments['drupalSettings'] = $assets->getSettings();
  135. $response->setAttachments($attachments);
  136. // Render the HTML to load these files, and add AJAX commands to insert this
  137. // HTML in the page. Settings are handled separately, afterwards.
  138. $settings = [];
  139. if (isset($js_assets_header['drupalSettings'])) {
  140. $settings = $js_assets_header['drupalSettings']['data'];
  141. unset($js_assets_header['drupalSettings']);
  142. }
  143. if (isset($js_assets_footer['drupalSettings'])) {
  144. $settings = $js_assets_footer['drupalSettings']['data'];
  145. unset($js_assets_footer['drupalSettings']);
  146. }
  147. // Prepend commands to add the assets, preserving their relative order.
  148. $resource_commands = [];
  149. if ($css_assets) {
  150. $css_render_array = $this->cssCollectionRenderer->render($css_assets);
  151. $resource_commands[] = new AddCssCommand($this->renderer->renderPlain($css_render_array));
  152. }
  153. if ($js_assets_header) {
  154. $js_header_render_array = $this->jsCollectionRenderer->render($js_assets_header);
  155. $resource_commands[] = new PrependCommand('head', $this->renderer->renderPlain($js_header_render_array));
  156. }
  157. if ($js_assets_footer) {
  158. $js_footer_render_array = $this->jsCollectionRenderer->render($js_assets_footer);
  159. $resource_commands[] = new AppendCommand('body', $this->renderer->renderPlain($js_footer_render_array));
  160. }
  161. foreach (array_reverse($resource_commands) as $resource_command) {
  162. $response->addCommand($resource_command, TRUE);
  163. }
  164. // Prepend a command to merge changes and additions to drupalSettings.
  165. if (!empty($settings)) {
  166. // During Ajax requests basic path-specific settings are excluded from
  167. // new drupalSettings values. The original page where this request comes
  168. // from already has the right values. An Ajax request would update them
  169. // with values for the Ajax request and incorrectly override the page's
  170. // values.
  171. // @see system_js_settings_alter()
  172. unset($settings['path']);
  173. $response->addCommand(new SettingsCommand($settings, TRUE), TRUE);
  174. }
  175. $commands = $response->getCommands();
  176. $this->moduleHandler->alter('ajax_render', $commands);
  177. return $commands;
  178. }
  179. }