diff --git a/core/authorize.php b/core/authorize.php index 0eb4c6a..4d9df40 100644 --- a/core/authorize.php +++ b/core/authorize.php @@ -166,8 +166,8 @@ function authorize_access_allowed(Request $request) { '#show_messages' => $show_messages, ))); - // Prepare attachments, because this does not go the usual way - // via the Symfony chain, but is send directly. + // Prepare attachments, because this does not go the usual way via the + // Symfony chain, but is sent directly. $bare_html_page_renderer->prepareAttachments($response); $response->send(); diff --git a/core/includes/batch.inc b/core/includes/batch.inc index 9c71f09..58a9c15 100644 --- a/core/includes/batch.inc +++ b/core/includes/batch.inc @@ -144,8 +144,8 @@ function _batch_progress_page() { // Use a HtmlResponse to process the attachments. $response = new HtmlResponse($content); - // Prepare attachments, because this does not go the usual way - // via the Symfony chain, but is send directly. + // Prepare attachments, because this does not go the usual way via the + // Symfony chain, but is sent directly. $bare_html_page_renderer->prepareAttachments($response); // Just use the content of the response. diff --git a/core/includes/errors.inc b/core/includes/errors.inc index 01c747c..de7a77d 100644 --- a/core/includes/errors.inc +++ b/core/includes/errors.inc @@ -248,8 +248,8 @@ function _drupal_log_error($error, $fatal = FALSE) { $output = $bare_html_page_renderer->renderBarePage(['#markup' => $message], 'Error', 'maintenance_page'); $response = new HtmlResponse($output, 500); - // Prepare attachments, because this does not go the usual way - // via the Symfony chain, but is send directly. + // Prepare attachments, because this does not go the usual way via the + // Symfony chain, but is sent directly. $bare_html_page_renderer->prepareAttachments($response); $response->setStatusCode(500, '500 Service unavailable (with message)'); diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 2166062..d0181b9 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -996,8 +996,8 @@ function install_display_output($output, $install_state) { $response->headers->add($default_headers); $response->setContent($bare_html_page_renderer->renderBarePage($output, $output['#title'], 'install_page', $regions)); - // Prepare attachments, because this does not go the usual way - // via the Symfony chain, but is send directly. + // Prepare attachments, because this does not go the usual way via the + // Symfony chain, but is sent directly. $bare_html_page_renderer->prepareAttachments($response); $response->send(); diff --git a/core/includes/theme.inc b/core/includes/theme.inc index ce95106..1e50907 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1314,17 +1314,20 @@ function template_preprocess_html(&$variables) { // Create placeholder strings for these keys. // @see \Drupal\Core\Render\HtmlResponseSubscriber - $keys = [ + $types = [ 'styles', 'scripts', 'scripts_bottom', 'head', ]; - foreach ($keys as $key) { + foreach ($types as $type) { $token = Crypt::randomBytesBase64(55); - $placeholder = ''; - $variables[$key]['#markup'] = $placeholder; - $variables[$key]['#attached']['html_response_placeholders'][$key] = $placeholder; + $placeholder = SafeMarkup::format('', [ + '@type' => $type, + '@token' => $token, + ]); + $variables[$type]['#markup'] = $placeholder; + $variables[$type]['#attached']['html_response_placeholders'][$type] = $placeholder; } } diff --git a/core/lib/Drupal/Core/EventSubscriber/HtmlResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/HtmlResponseSubscriber.php index 8bb245b..a3f8db4 100644 --- a/core/lib/Drupal/Core/EventSubscriber/HtmlResponseSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/HtmlResponseSubscriber.php @@ -14,6 +14,7 @@ use Drupal\Core\Config\Config; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Render\AttachmentsResponseInterface; +use Drupal\Core\Render\HtmlResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; @@ -22,7 +23,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** - * Response subscriber to handle html responses. + * Response subscriber to handle HTML responses. */ class HtmlResponseSubscriber implements EventSubscriberInterface { @@ -88,71 +89,132 @@ public function onRespond(FilterResponseEvent $event) { } $response = $event->getResponse(); - $this->prepareAttachments($response); + if ($response instanceof HtmlResponse) { + $this->prepareAttachments($response); + } } - public function prepareAttachments($response) { - if ($response instanceof AttachmentsResponseInterface && !($response instanceof AjaxResponse)) { - // Render the attachments into HTML markup - $attached = $response->getAttachments(); + /** + * Prepare attachments contained in a HtmlResponse. + * + * This renders them into HTML markup and replaces the HTML response + * placeholders in the content of the response. + * + * It also sets any headers found in the attachments' "http_header" key. + * + * @param \Drupal\Core\Render\AttachmentsResponseInterface $response + * The response containing attachments. + */ + public function prepareAttachments(AttachmentsResponseInterface $response) { + $attached = $response->getAttachments(); - // Get the placeholders from attached and then remove them. - $placeholders = $attached['html_response_placeholders']; - unset($attached['html_response_placeholders']); + // Get the placeholders from attached and then remove them. + $placeholders = $attached['html_response_placeholders']; + unset($attached['html_response_placeholders']); - $all_attached = ['#attached' => $attached]; - $assets = AttachedAssets::createFromRenderArray($all_attached); + $variables = []; + $variables += $this->processAssetLibraries($attached, $placeholders); - // 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']) : []); + // Handle all non-asset attachments. + $all_attached = ['#attached' => $attached]; + drupal_process_attached($all_attached); - $variables = []; + // Get HTML head elements - if present. + if (isset($placeholders['head'])) { + $variables['head'] = drupal_get_html_head(FALSE); + } - // 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)); - } + // Now replace the placeholders in the response content with the real + // data. + $this->renderPlaceholders($response, $placeholders, $variables); - // 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') && $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); - } + // Finally set the headers on the response. + $headers = drupal_get_http_header(); + $this->setHeaders($response, $headers); + } - // Handle all non-asset attachments. - // @todo Remove and move into this class. - drupal_process_attached($all_attached); - - $headers = drupal_get_http_header(); - foreach ($headers as $name => $value) { - // Symfony special-cases the 'Status' header. - if ($name === 'status') { - $response->setStatusCode($value); - } - $response->headers->set($name, $value, FALSE); - } + /** + * 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 HTML head elements - if present. - if (isset($placeholders['head'])) { - $variables['head'] = drupal_get_html_head(FALSE); - } + // 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') && $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; + } - // Now replace the placeholders in the response content with the real - // data. - $content = $response->getContent(); - foreach ($placeholders as $key => $placeholder) { - $content = str_replace($placeholder, render($variables[$key]), $content); + /** + * Renders variables into HTML markup and replaces placeholders in the + * response content. + * + * @param \Drupal\Core\Render\AttachmentsResponseInterface $response + * The response object. + * @param array placeholders + * An array of placeholders, keyed by type with the placeholders + * present in the content of the response as values. + * @param array variables + * The variables to render and replace, keyed by type with renderable + * arrays as values. + */ + protected function renderPlaceholders(AttachmentsResponseInterface $response, array $placeholders, array $variables) { + $content = $response->getContent(); + foreach ($placeholders as $key => $placeholder) { + $content = str_replace($placeholder, render($variables[$key]), $content); + } + $response->setContent($content); + } + + /** + * Sets headers on a response object. + * + * @param \Drupal\Core\Render\AttachmentsResponseInterface $response + * The response object. + * @param array $headers + * The headers to set. + */ + protected function setHeaders(AttachmentsResponseInterface $response, array $headers) { + foreach ($headers as $name => $value) { + // Symfony special-cases the 'Status' header. + if ($name === 'status') { + $response->setStatusCode($value); } - $response->setContent($content); + $response->headers->set($name, $value, FALSE); } } @@ -166,4 +228,5 @@ public static function getSubscribedEvents() { $events[KernelEvents::RESPONSE][] = array('onRespond'); return $events; } + } diff --git a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php index b07a29d..98de48f 100644 --- a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php +++ b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php @@ -79,8 +79,6 @@ public function renderBarePage(array $content, $title, $page_theme_property, arr $this->renderer->renderRoot($html); $content = $this->renderCache->getCacheableRenderArray($html); - // Never cache exception pages. - $content['#cache']['max-age'] = 0; return $content; } diff --git a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php index 10f0d08..c7ed405 100644 --- a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php +++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php @@ -120,7 +120,8 @@ public function renderResponse(array $main_content, Request $request, RouteMatch // page.html.twig, hence add them here, just before rendering html.html.twig. $this->buildPageTopAndBottom($html); - // @todo Make renderRoot return a cacheable render array directly. + // @todo https://www.drupal.org/node/2495001 Make renderRoot return a + // cacheable render array directly. $this->renderer->renderRoot($html); $content = $this->renderCache->getCacheableRenderArray($html); diff --git a/core/modules/views/views.module b/core/modules/views/views.module index cfd2ab9..5fdd368 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -291,11 +291,6 @@ function views_theme_suggestions_container_alter(array &$suggestions, array $var * Implements MODULE_preprocess_HOOK(). */ function views_preprocess_html(&$variables) { - // Early-return to prevent adding unnecessary JavaScript. - if (!\Drupal::moduleHandler()->moduleExists('contextual') || !\Drupal::currentUser()->hasPermission('access contextual links')) { - return; - } - // If the main content of this page contains a view, attach its contextual // links to the overall page array. This allows them to be rendered directly // next to the page title. @@ -303,6 +298,11 @@ function views_preprocess_html(&$variables) { views_add_contextual_links($variables['page'], 'page', $view, $view->current_display); } + // Early-return to prevent adding unnecessary JavaScript. + if (!\Drupal::moduleHandler()->moduleExists('contextual') || !\Drupal::currentUser()->hasPermission('access contextual links')) { + return; + } + // If the page contains a view as its main content, contextual links may have // been attached to the page as a whole; for example, by // views_page_display_pre_render().