示例#1
0
 /**
  * Verifies that the datepicker can be localized.
  *
  * @see locale_library_alter()
  */
 public function testLibraryAlter()
 {
     $assets = new AttachedAssets();
     $assets->setLibraries(['core/jquery.ui.datepicker']);
     $js_assets = $this->container->get('asset.resolver')->getJsAssets($assets, FALSE)[1];
     $this->assertTrue(array_key_exists('core/modules/locale/locale.datepicker.js', $js_assets), 'locale.datepicker.js added to scripts.');
 }
 /**
  * Processes the content for output.
  *
  * If content is a render array, it may contain attached assets to be
  * processed.
  *
  * @return string|\Drupal\Component\Utility\SafeStringInterface
  *   HTML rendered content.
  */
 protected function getRenderedContent()
 {
     $this->attachedAssets = new AttachedAssets();
     if (is_array($this->content)) {
         $html = \Drupal::service('renderer')->renderRoot($this->content);
         $this->attachedAssets = AttachedAssets::createFromRenderArray($this->content);
         return $html;
     } else {
         return $this->content;
     }
 }
示例#3
0
 /**
  * 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_page_state = $request->request->get('ajax_page_state');
     // Aggregate CSS/JS if necessary, but only during normal site operation.
     $config = \Drupal::config('system.performance');
     $optimize_css = !defined('MAINTENANCE_MODE') && $config->get('css.preprocess');
     $optimize_js = !defined('MAINTENANCE_MODE') && $config->get('js.preprocess');
     // Resolve the attached libraries into asset collections.
     $assets = new AttachedAssets();
     $assets->setLibraries(isset($this->attachments['library']) ? $this->attachments['library'] : [])->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : [])->setSettings(isset($this->attachments['drupalSettings']) ? $this->attachments['drupalSettings'] : []);
     $asset_resolver = \Drupal::service('asset.resolver');
     $css_assets = $asset_resolver->getCssAssets($assets, $optimize_css);
     list($js_assets_header, $js_assets_footer) = $asset_resolver->getJsAssets($assets, $optimize_js);
     // Render the HTML to load these files, and add AJAX commands to insert this
     // HTML in the page. Settings are handled separately, afterwards.
     $settings = [];
     if (isset($js_assets_header['drupalSettings'])) {
         $settings = $js_assets_header['drupalSettings']['data'];
         unset($js_assets_header['drupalSettings']);
     }
     if (isset($js_assets_footer['drupalSettings'])) {
         $settings = $js_assets_footer['drupalSettings']['data'];
         unset($js_assets_footer['drupalSettings']);
     }
     // Prepend commands to add the assets, preserving their relative order.
     $resource_commands = array();
     $renderer = $this->getRenderer();
     if (!empty($css_assets)) {
         $css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css_assets);
         $resource_commands[] = new AddCssCommand($renderer->render($css_render_array));
     }
     if (!empty($js_assets_header)) {
         $js_header_render_array = \Drupal::service('asset.js.collection_renderer')->render($js_assets_header);
         $resource_commands[] = new PrependCommand('head', $renderer->render($js_header_render_array));
     }
     if (!empty($js_assets_footer)) {
         $js_footer_render_array = \Drupal::service('asset.js.collection_renderer')->render($js_assets_footer);
         $resource_commands[] = new AppendCommand('body', $renderer->render($js_footer_render_array));
     }
     foreach (array_reverse($resource_commands) as $resource_command) {
         $this->addCommand($resource_command, TRUE);
     }
     // Prepend a command to merge changes and additions to drupalSettings.
     if (!empty($settings)) {
         // 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. An Ajax request would update them
         // with values for the Ajax request and incorrectly override the page's
         // values.
         // @see system_js_settings_alter()
         unset($settings['path']);
         $this->addCommand(new SettingsCommand($settings, TRUE), TRUE);
     }
     $commands = $this->commands;
     \Drupal::moduleHandler()->alter('ajax_render', $commands);
     return $commands;
 }
 /**
  * Tests JavaScript files that have querystrings attached get added right.
  */
 function testAddJsFileWithQueryString()
 {
     $build['#attached']['library'][] = 'common_test/querystring';
     $assets = AttachedAssets::createFromRenderArray($build);
     $css = $this->assetResolver->getCssAssets($assets, FALSE);
     $js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
     $this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/querystring.css?arg1=value1&arg2=value2', $css), 'CSS file with query string is correctly added.');
     $this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2', $js), 'JavaScript file with query string is correctly added.');
     $css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css);
     $rendered_css = $this->renderer->renderPlain($css_render_array);
     $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
     $rendered_js = $this->renderer->renderPlain($js_render_array);
     $query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
     $this->assertNotIdentical(strpos($rendered_css, '<link rel="stylesheet" href="' . str_replace('&', '&amp;', file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/querystring.css?arg1=value1&arg2=value2'))) . '&amp;' . $query_string . '" media="all" />'), FALSE, 'CSS file with query string gets version query string correctly appended..');
     $this->assertNotIdentical(strpos($rendered_js, '<script src="' . str_replace('&', '&amp;', file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2'))) . '&amp;' . $query_string . '"></script>'), FALSE, 'JavaScript file with query string gets version query string correctly appended.');
 }
 /**
  * {@inheritdoc}
  */
 public function processAttachments(AttachmentsInterface $response)
 {
     // @todo Convert to assertion once https://www.drupal.org/node/2408013 lands
     if (!$response instanceof HtmlResponse) {
         throw new \InvalidArgumentException('\\Drupal\\Core\\Render\\HtmlResponse instance expected.');
     }
     // First, render the actual placeholders; this may cause additional
     // attachments to be added to the response, which the attachment
     // placeholders rendered by renderHtmlResponseAttachmentPlaceholders() will
     // need to include.
     //
     // @todo Exceptions should not be used for code flow control. However, the
     //   Form API does not integrate with the HTTP Kernel based architecture of
     //   Drupal 8. In order to resolve this issue properly it is necessary to
     //   completely separate form submission from rendering.
     //   @see https://www.drupal.org/node/2367555
     try {
         $response = $this->renderPlaceholders($response);
     } catch (EnforcedResponseException $e) {
         return $e->getResponse();
     }
     // Get a reference to the attachments.
     $attached = $response->getAttachments();
     // Send a message back if the render array has unsupported #attached types.
     $unsupported_types = array_diff(array_keys($attached), ['html_head', 'feed', 'html_head_link', 'http_header', 'library', 'html_response_attachment_placeholders', 'placeholders', 'drupalSettings']);
     if (!empty($unsupported_types)) {
         throw new \LogicException(sprintf('You are not allowed to use %s in #attached.', implode(', ', $unsupported_types)));
     }
     // If we don't have any placeholders, there is no need to proceed.
     if (!empty($attached['html_response_attachment_placeholders'])) {
         // Get the placeholders from attached and then remove them.
         $attachment_placeholders = $attached['html_response_attachment_placeholders'];
         unset($attached['html_response_attachment_placeholders']);
         $assets = AttachedAssets::createFromRenderArray(['#attached' => $attached]);
         // Take Ajax page state into account, to allow for something like
         // Turbolinks to be implemented without altering core.
         // @see https://github.com/rails/turbolinks/
         $ajax_page_state = $this->requestStack->getCurrentRequest()->get('ajax_page_state');
         $assets->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : []);
         $variables = $this->processAssetLibraries($assets, $attachment_placeholders);
         // $variables now contains the markup to load the asset libraries. Update
         // $attached with the final list of libraries and JavaScript settings, so
         // that $response can be updated with those. Then the response object will
         // list the final, processed attachments.
         $attached['library'] = $assets->getLibraries();
         $attached['drupalSettings'] = $assets->getSettings();
         // Since we can only replace content in the HTML head section if there's a
         // placeholder for it, we can safely avoid processing the render array if
         // it's not present.
         if (!empty($attachment_placeholders['head'])) {
             // 'feed' is a special case of 'html_head_link'. We process them into
             // 'html_head_link' entries and merge them.
             if (!empty($attached['feed'])) {
                 $attached = BubbleableMetadata::mergeAttachments($attached, $this->processFeed($attached['feed']));
                 unset($attached['feed']);
             }
             // 'html_head_link' is a special case of 'html_head' which can be present
             // as a head element, but also as a Link: HTTP header depending on
             // settings in the render array. Processing it can add to both the
             // 'html_head' and 'http_header' keys of '#attached', so we must address
             // it before 'html_head'.
             if (!empty($attached['html_head_link'])) {
                 // Merge the processed 'html_head_link' into $attached so that its
                 // 'html_head' and 'http_header' values are present for further
                 // processing.
                 $attached = BubbleableMetadata::mergeAttachments($attached, $this->processHtmlHeadLink($attached['html_head_link']));
                 unset($attached['html_head_link']);
             }
             // Now we can process 'html_head', which contains both 'feed' and
             // 'html_head_link'.
             if (!empty($attached['html_head'])) {
                 $variables['head'] = $this->processHtmlHead($attached['html_head']);
             }
         }
         // Now replace the attachment placeholders.
         $this->renderHtmlResponseAttachmentPlaceholders($response, $attachment_placeholders, $variables);
     }
     // Set the HTTP headers and status code on the response if any bubbled.
     if (!empty($attached['http_header'])) {
         $this->setHeaders($response, $attached['http_header']);
     }
     // AttachmentsResponseProcessorInterface mandates that the response it
     // processes contains the final attachment values.
     $response->setAttachments($attached);
     return $response;
 }
 /**
  * {@inheritdoc}
  */
 public function sendContent($content, array $attachments)
 {
     // First, gather the BigPipe placeholders that must be replaced.
     $placeholders = isset($attachments['big_pipe_placeholders']) ? $attachments['big_pipe_placeholders'] : [];
     $nojs_placeholders = isset($attachments['big_pipe_nojs_placeholders']) ? $attachments['big_pipe_nojs_placeholders'] : [];
     // BigPipe sends responses using "Transfer-Encoding: chunked". To avoid
     // sending already-sent assets, it is necessary to track cumulative assets
     // from all previously rendered/sent chunks.
     // @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.41
     $cumulative_assets = AttachedAssets::createFromRenderArray(['#attached' => $attachments]);
     $cumulative_assets->setAlreadyLoadedLibraries($attachments['library']);
     // The content in the placeholders may depend on the session, and by the
     // time the response is sent (see index.php), the session is already closed.
     // Reopen it for the duration that we are rendering placeholders.
     $this->session->start();
     list($pre_body, $post_body) = explode('</body>', $content, 2);
     $this->sendPreBody($pre_body, $nojs_placeholders, $cumulative_assets);
     $this->sendPlaceholders($placeholders, $this->getPlaceholderOrder($pre_body), $cumulative_assets);
     $this->sendPostBody($post_body);
     // Close the session again.
     $this->session->save();
     return $this;
 }
示例#7
0
 /**
  * Gathers the markup for each type of asset.
  *
  * @param array $render_array
  *   The render array for the entity.
  *
  * @return array
  *   An array of rendered assets. See self::getRenderedEntity() for the keys.
  *
  * @todo template_preprocess_html() should be split up and made reusable.
  */
 protected function gatherAssetMarkup(array $render_array)
 {
     $assets = AttachedAssets::createFromRenderArray($render_array);
     // Render the asset collections.
     $css_assets = $this->assetResolver->getCssAssets($assets, FALSE);
     $variables['styles'] = $this->cssCollectionRenderer->render($css_assets);
     list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, FALSE);
     $variables['scripts'] = $this->jsCollectionRenderer->render($js_assets_header);
     $variables['scripts_bottom'] = $this->jsCollectionRenderer->render($js_assets_footer);
     // @todo Handle all non-asset attachments.
     //$variables['head'] = drupal_get_html_head(FALSE);
     return $variables;
 }
 /**
  * Processes asset libraries into render arrays.
  *
  * @param array $attached
  *   The attachments to process.
  * @param array $placeholders
  *   The placeholders that exist in the response.
  *
  * @return array
  *   An array keyed by asset type, with keys:
  *     - styles
  *     - scripts
  *     - scripts_bottom
  */
 protected function processAssetLibraries(array $attached, array $placeholders)
 {
     $all_attached = ['#attached' => $attached];
     $assets = AttachedAssets::createFromRenderArray($all_attached);
     // Take Ajax page state into account, to allow for something like Turbolinks
     // to be implemented without altering core.
     // @see https://github.com/rails/turbolinks/
     // @todo https://www.drupal.org/node/2497115 - Below line is broken due to ->request.
     $ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state');
     $assets->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : []);
     $variables = [];
     // Print styles - if present.
     if (isset($placeholders['styles'])) {
         // Optimize CSS if necessary, but only during normal site operation.
         $optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess');
         $variables['styles'] = $this->cssCollectionRenderer->render($this->assetResolver->getCssAssets($assets, $optimize_css));
     }
     // Print scripts - if any are present.
     if (isset($placeholders['scripts']) || isset($placeholders['scripts_bottom'])) {
         // Optimize JS if necessary, but only during normal site operation.
         $optimize_js = !defined('MAINTENANCE_MODE') && !\Drupal::state()->get('system.maintenance_mode') && $this->config->get('js.preprocess');
         list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, $optimize_js);
         $variables['scripts'] = $this->jsCollectionRenderer->render($js_assets_header);
         $variables['scripts_bottom'] = $this->jsCollectionRenderer->render($js_assets_footer);
     }
     return $variables;
 }
示例#9
0
 /**
  * Tests that new JavaScript and CSS files are lazy-loaded on an AJAX request.
  */
 public function testLazyLoad()
 {
     $asset_resolver = \Drupal::service('asset.resolver');
     $css_collection_renderer = \Drupal::service('asset.css.collection_renderer');
     $js_collection_renderer = \Drupal::service('asset.js.collection_renderer');
     $renderer = \Drupal::service('renderer');
     $expected = array('setting_name' => 'ajax_forms_test_lazy_load_form_submit', 'setting_value' => 'executed', 'library_1' => 'system/admin', 'library_2' => 'system/drupal.system');
     // Get the base page.
     $this->drupalGet('ajax_forms_test_lazy_load_form');
     $original_settings = $this->getDrupalSettings();
     $original_libraries = explode(',', $original_settings['ajaxPageState']['libraries']);
     // Verify that the base page doesn't have the settings and files that are to
     // be lazy loaded as part of the next requests.
     $this->assertTrue(!isset($original_settings[$expected['setting_name']]), format_string('Page originally lacks the %setting, as expected.', array('%setting' => $expected['setting_name'])));
     $this->assertTrue(!in_array($expected['library_1'], $original_libraries), format_string('Page originally lacks the %library library, as expected.', array('%library' => $expected['library_1'])));
     $this->assertTrue(!in_array($expected['library_2'], $original_libraries), format_string('Page originally lacks the %library library, as expected.', array('%library' => $expected['library_2'])));
     // Calculate the expected CSS and JS.
     $assets = new AttachedAssets();
     $assets->setLibraries([$expected['library_1']])->setAlreadyLoadedLibraries($original_libraries);
     $css_render_array = $css_collection_renderer->render($asset_resolver->getCssAssets($assets, FALSE));
     $expected_css_html = $renderer->render($css_render_array);
     $assets->setLibraries([$expected['library_2']])->setAlreadyLoadedLibraries($original_libraries);
     $js_assets = $asset_resolver->getJsAssets($assets, FALSE)[1];
     unset($js_assets['drupalSettings']);
     $js_render_array = $js_collection_renderer->render($js_assets);
     $expected_js_html = $renderer->render($js_render_array);
     // Submit the AJAX request without triggering files getting added.
     $commands = $this->drupalPostAjaxForm(NULL, array('add_files' => FALSE), array('op' => t('Submit')));
     $new_settings = $this->getDrupalSettings();
     $new_libraries = explode(',', $new_settings['ajaxPageState']['libraries']);
     // Verify the setting was not added when not expected.
     $this->assertTrue(!isset($new_settings[$expected['setting_name']]), format_string('Page still lacks the %setting, as expected.', array('%setting' => $expected['setting_name'])));
     $this->assertTrue(!in_array($expected['library_1'], $new_libraries), format_string('Page still lacks the %library library, as expected.', array('%library' => $expected['library_1'])));
     $this->assertTrue(!in_array($expected['library_2'], $new_libraries), format_string('Page still lacks the %library library, as expected.', array('%library' => $expected['library_2'])));
     // Verify a settings command does not add CSS or scripts to drupalSettings
     // and no command inserts the corresponding tags on the page.
     $found_settings_command = FALSE;
     $found_markup_command = FALSE;
     foreach ($commands as $command) {
         if ($command['command'] == 'settings' && (array_key_exists('css', $command['settings']['ajaxPageState']) || array_key_exists('js', $command['settings']['ajaxPageState']))) {
             $found_settings_command = TRUE;
         }
         if (isset($command['data']) && ($command['data'] == $expected_js_html || $command['data'] == $expected_css_html)) {
             $found_markup_command = TRUE;
         }
     }
     $this->assertFalse($found_settings_command, format_string('Page state still lacks the %library_1 and %library_2 libraries, as expected.', array('%library_1' => $expected['library_1'], '%library_2' => $expected['library_2'])));
     $this->assertFalse($found_markup_command, format_string('Page still lacks the %library_1 and %library_2 libraries, as expected.', array('%library_1' => $expected['library_1'], '%library_2' => $expected['library_2'])));
     // Submit the AJAX request and trigger adding files.
     $commands = $this->drupalPostAjaxForm(NULL, array('add_files' => TRUE), array('op' => t('Submit')));
     $new_settings = $this->getDrupalSettings();
     $new_libraries = explode(',', $new_settings['ajaxPageState']['libraries']);
     // Verify the expected setting was added, both to drupalSettings, and as
     // the first AJAX command.
     $this->assertIdentical($new_settings[$expected['setting_name']], $expected['setting_value'], format_string('Page now has the %setting.', array('%setting' => $expected['setting_name'])));
     $expected_command = new SettingsCommand(array($expected['setting_name'] => $expected['setting_value']), TRUE);
     $this->assertCommand(array_slice($commands, 0, 1), $expected_command->render(), format_string('The settings command was first.'));
     // Verify the expected CSS file was added, both to drupalSettings, and as
     // the second AJAX command for inclusion into the HTML.
     $this->assertTrue(in_array($expected['library_1'], $new_libraries), format_string('Page state now has the %library library.', array('%library' => $expected['library_1'])));
     $this->assertCommand(array_slice($commands, 1, 1), array('data' => $expected_css_html), format_string('Page now has the %library library.', array('%library' => $expected['library_1'])));
     // Verify the expected JS file was added, both to drupalSettings, and as
     // the third AJAX command for inclusion into the HTML. By testing for an
     // exact HTML string containing the SCRIPT tag, we also ensure that
     // unexpected JavaScript code, such as a jQuery.extend() that would
     // potentially clobber rather than properly merge settings, didn't
     // accidentally get added.
     $this->assertTrue(in_array($expected['library_2'], $new_libraries), format_string('Page state now has the %library library.', array('%library' => $expected['library_2'])));
     $this->assertCommand(array_slice($commands, 2, 1), array('data' => $expected_js_html), format_string('Page now has the %library library.', array('%library' => $expected['library_2'])));
 }
 /**
  * Generate the HTML for our entity.
  *
  * @param \Drupal\Core\Entity\ContentEntityInterface $entity
  *   The entity we're rendering.
  * @param bool $use_default_css
  *   TRUE if we should inject our default CSS otherwise FALSE.
  * @param bool $optimize_css
  *   TRUE if we should compress the CSS otherwise FALSE.
  *
  * @return string
  *   The generated HTML.
  *
  * @throws \Exception
  */
 protected function getHtml(ContentEntityInterface $entity, $use_default_css, $optimize_css)
 {
     $render_controller = $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId());
     $render = ['#theme' => 'entity_print__' . $entity->getEntityTypeId() . '__' . $entity->id(), '#entity' => $entity, '#entity_array' => $render_controller->view($entity, 'pdf'), '#attached' => []];
     // Inject some generic CSS across all templates.
     if ($use_default_css) {
         $render['#attached']['library'][] = 'entity_print/default';
     }
     // Allow other modules to add their own CSS.
     $this->moduleHandler->alter('entity_print_css', $render, $entity);
     // Inject CSS from the theme info files and then render the CSS.
     $render = $this->addCss($render, $entity);
     $css_assets = $this->assetResolver->getCssAssets(AttachedAssets::createFromRenderArray($render), $optimize_css);
     $rendered_css = $this->cssRenderer->render($css_assets);
     $render['#entity_print_css'] = $this->renderer->render($rendered_css);
     return $this->renderer->render($render);
 }
 /**
  * Prepares the AJAX commands to attach assets.
  *
  * @param \Drupal\Core\Ajax\AjaxResponse $response
  *   The AJAX response to update.
  * @param \Symfony\Component\HttpFoundation\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 buildAttachmentsCommands(AjaxResponse $response, Request $request)
 {
     $ajax_page_state = $request->request->get('ajax_page_state');
     // Aggregate CSS/JS if necessary, but only during normal site operation.
     $optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess');
     $optimize_js = !defined('MAINTENANCE_MODE') && $this->config->get('js.preprocess');
     $attachments = $response->getAttachments();
     // Resolve the attached libraries into asset collections.
     $assets = new AttachedAssets();
     $assets->setLibraries(isset($attachments['library']) ? $attachments['library'] : [])->setAlreadyLoadedLibraries(isset($ajax_page_state['libraries']) ? explode(',', $ajax_page_state['libraries']) : [])->setSettings(isset($attachments['drupalSettings']) ? $attachments['drupalSettings'] : []);
     $css_assets = $this->assetResolver->getCssAssets($assets, $optimize_css);
     list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, $optimize_js);
     // First, AttachedAssets::setLibraries() ensures duplicate libraries are
     // removed: it converts it to a set of libraries if necessary. Second,
     // AssetResolver::getJsSettings() ensures $assets contains the final set of
     // JavaScript settings. AttachmentsResponseProcessorInterface also mandates
     // that the response it processes contains the final attachment values, so
     // update both the 'library' and 'drupalSettings' attachments accordingly.
     $attachments['library'] = $assets->getLibraries();
     $attachments['drupalSettings'] = $assets->getSettings();
     $response->setAttachments($attachments);
     // Render the HTML to load these files, and add AJAX commands to insert this
     // HTML in the page. Settings are handled separately, afterwards.
     $settings = [];
     if (isset($js_assets_header['drupalSettings'])) {
         $settings = $js_assets_header['drupalSettings']['data'];
         unset($js_assets_header['drupalSettings']);
     }
     if (isset($js_assets_footer['drupalSettings'])) {
         $settings = $js_assets_footer['drupalSettings']['data'];
         unset($js_assets_footer['drupalSettings']);
     }
     // Prepend commands to add the assets, preserving their relative order.
     $resource_commands = array();
     if ($css_assets) {
         $css_render_array = $this->cssCollectionRenderer->render($css_assets);
         $resource_commands[] = new AddCssCommand($this->renderer->renderPlain($css_render_array));
     }
     if ($js_assets_header) {
         $js_header_render_array = $this->jsCollectionRenderer->render($js_assets_header);
         $resource_commands[] = new PrependCommand('head', $this->renderer->renderPlain($js_header_render_array));
     }
     if ($js_assets_footer) {
         $js_footer_render_array = $this->jsCollectionRenderer->render($js_assets_footer);
         $resource_commands[] = new AppendCommand('body', $this->renderer->renderPlain($js_footer_render_array));
     }
     foreach (array_reverse($resource_commands) as $resource_command) {
         $response->addCommand($resource_command, TRUE);
     }
     // Prepend a command to merge changes and additions to drupalSettings.
     if (!empty($settings)) {
         // 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. An Ajax request would update them
         // with values for the Ajax request and incorrectly override the page's
         // values.
         // @see system_js_settings_alter()
         unset($settings['path']);
         $response->addCommand(new SettingsCommand($settings, TRUE), TRUE);
     }
     $commands = $response->getCommands();
     $this->moduleHandler->alter('ajax_render', $commands);
     return $commands;
 }