diff --git a/core/authorize.php b/core/authorize.php index 35277f6..0eb4c6a 100644 --- a/core/authorize.php +++ b/core/authorize.php @@ -20,6 +20,7 @@ * @link authorize Authorized operation helper functions @endlink */ +use Drupal\Core\Render\HtmlResponse; use Drupal\Core\DrupalKernel; use Drupal\Core\Url; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; @@ -85,7 +86,7 @@ function authorize_access_allowed(Request $request) { $content = []; $show_messages = TRUE; -$response = new Response(); +$response = new HtmlResponse(); if (authorize_access_allowed($request)) { // Load both the Form API and Batch API. require_once __DIR__ . '/includes/form.inc'; @@ -159,9 +160,15 @@ function authorize_access_allowed(Request $request) { } if (!empty($content)) { + $bare_html_page_renderer = \Drupal::service('bare_html_page_renderer'); $response->headers->set('Content-Type', 'text/html; charset=utf-8'); - $response->setContent(\Drupal::service('bare_html_page_renderer')->renderBarePage($content, $page_title, 'maintenance_page', array( + $response->setContent($bare_html_page_renderer->renderBarePage($content, $page_title, 'maintenance_page', array( '#show_messages' => $show_messages, ))); + + // Prepare attachments, because this does not go the usual way + // via the Symfony chain, but is send directly. + $bare_html_page_renderer->prepareAttachments($response); + $response->send(); } diff --git a/core/core.services.yml b/core/core.services.yml index 58db84c..e3c8bb7 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -897,7 +897,7 @@ services: arguments: ['@router', '@router.request_context', NULL, '@request_stack'] bare_html_page_renderer: class: Drupal\Core\Render\BareHtmlPageRenderer - arguments: ['@renderer'] + arguments: ['@renderer', '@render_cache', '@html_response_subscriber'] lazy: true private_key: class: Drupal\Core\PrivateKey @@ -969,6 +969,11 @@ services: tags: - { name: event_subscriber } arguments: ['@current_user'] + html_response_subscriber: + class: Drupal\Core\EventSubscriber\HtmlResponseSubscriber + tags: + - { name: event_subscriber } + arguments: ['@asset.resolver', '@config.factory', '@asset.css.collection_renderer', '@asset.js.collection_renderer', '@request_stack'] finish_response_subscriber: class: Drupal\Core\EventSubscriber\FinishResponseSubscriber tags: diff --git a/core/includes/batch.inc b/core/includes/batch.inc index 7d905cd..9c71f09 100644 --- a/core/includes/batch.inc +++ b/core/includes/batch.inc @@ -19,6 +19,7 @@ use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Batch\Percentage; use Drupal\Core\Form\FormState; +use Drupal\Core\Render\HtmlResponse; use Drupal\Core\Url; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -135,9 +136,21 @@ function _batch_progress_page() { // additional HTML output by PHP shows up inside the page rather than below // it. While this causes invalid HTML, the same would be true if we didn't, // as content is not allowed to appear after anyway. - $fallback = \Drupal::service('bare_html_page_renderer')->renderBarePage(['#markup' => $fallback], $current_set['title'], 'maintenance_page', array( + $bare_html_page_renderer = \Drupal::service('bare_html_page_renderer'); + $content = $bare_html_page_renderer->renderBarePage(['#markup' => $fallback], $current_set['title'], 'maintenance_page', array( '#show_messages' => FALSE, )); + + // 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. + $bare_html_page_renderer->prepareAttachments($response); + + // Just use the content of the response. + $fallback = $response->getContent(); + list($fallback) = explode('', $fallback); print $fallback; diff --git a/core/includes/errors.inc b/core/includes/errors.inc index 5e38a97..01c747c 100644 --- a/core/includes/errors.inc +++ b/core/includes/errors.inc @@ -8,8 +8,8 @@ use Drupal\Component\Utility\SafeMarkup; use Drupal\Component\Utility\Xss; use Drupal\Core\Logger\RfcLogLevel; +use Drupal\Core\Render\HtmlResponse; use Drupal\Core\Utility\Error; -use Symfony\Component\HttpFoundation\Response; /** * Maps PHP error constants to watchdog severity levels. @@ -241,12 +241,17 @@ function _drupal_log_error($error, $fatal = FALSE) { '#markup' => $message, ); install_display_output($output, $GLOBALS['install_state']); + exit; } - else { - $output = \Drupal::service('bare_html_page_renderer')->renderBarePage(['#markup' => $message], 'Error', 'maintenance_page'); - } - $response = new Response($output, 500); + $bare_html_page_renderer = \Drupal::service('bare_html_page_renderer'); + $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. + $bare_html_page_renderer->prepareAttachments($response); + $response->setStatusCode(500, '500 Service unavailable (with message)'); // An exception must halt script execution. $response->send(); diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 6f1add0..2166062 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -15,6 +15,7 @@ use Drupal\Core\Language\Language; use Drupal\Core\Language\LanguageManager; use Drupal\Core\Logger\LoggerChannelFactory; +use Drupal\Core\Render\HtmlResponse; use Drupal\Core\Site\Settings; use Drupal\Core\StringTranslation\Translator\FileTranslation; use Drupal\Core\Extension\ExtensionDiscovery; @@ -984,7 +985,8 @@ function install_display_output($output, $install_state) { $regions['sidebar_first'] = $task_list; } - $response = new Response(); + $bare_html_page_renderer = \Drupal::service('bare_html_page_renderer'); + $response = new HtmlResponse(); $default_headers = array( 'Expires' => 'Sun, 19 Nov 1978 05:00:00 GMT', 'Last-Modified' => gmdate(DATE_RFC1123, REQUEST_TIME), @@ -992,7 +994,12 @@ function install_display_output($output, $install_state) { 'ETag' => '"' . REQUEST_TIME . '"', ); $response->headers->add($default_headers); - $response->setContent(\Drupal::service('bare_html_page_renderer')->renderBarePage($output, $output['#title'], 'install_page', $regions)); + $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. + $bare_html_page_renderer->prepareAttachments($response); + $response->send(); exit; } diff --git a/core/includes/theme.inc b/core/includes/theme.inc index d8f3136..ce95106 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -9,6 +9,7 @@ */ use Drupal\Component\Serialization\Json; +use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\Html; use Drupal\Component\Utility\SafeMarkup; use Drupal\Component\Utility\Unicode; @@ -1311,41 +1312,20 @@ function template_preprocess_html(&$variables) { // @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0. $variables['head_title_array'] = $head_title; - // Collect all attachments. This must happen in the preprocess function for - // #type => html, to ensure that attachments added in #pre_render callbacks - // for #type => html are included. - $attached = $variables['html']['#attached']; - $attached = drupal_merge_attached($attached, $variables['page']['#attached']); - if (isset($variables['page_top'])) { - $attached = drupal_merge_attached($attached, $variables['page_top']['#attached']); + // Create placeholder strings for these keys. + // @see \Drupal\Core\Render\HtmlResponseSubscriber + $keys = [ + 'styles', + 'scripts', + 'scripts_bottom', + 'head', + ]; + foreach ($keys as $key) { + $token = Crypt::randomBytesBase64(55); + $placeholder = ''; + $variables[$key]['#markup'] = $placeholder; + $variables[$key]['#attached']['html_response_placeholders'][$key] = $placeholder; } - if (isset($variables['page_bottom'])) { - $attached = drupal_merge_attached($attached, $variables['page_bottom']['#attached']); - } - - // Render the attachments into HTML markup to be used directly in the template - // for #type => html: html.html.twig. - $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/ - $ajax_page_state = \Drupal::request()->request->get('ajax_page_state'); - $assets->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : []); - // Optimize CSS/JS if necessary, but only during normal site operation. - $optimize_css = !defined('MAINTENANCE_MODE') && \Drupal::config('system.performance')->get('css.preprocess'); - $optimize_js = !defined('MAINTENANCE_MODE') && \Drupal::config('system.performance')->get('js.preprocess'); - // Render the asset collections. - $asset_resolver = \Drupal::service('asset.resolver'); - $variables['styles'] = \Drupal::service('asset.css.collection_renderer')->render($asset_resolver->getCssAssets($assets, $optimize_css)); - list($js_assets_header, $js_assets_footer) = $asset_resolver->getJsAssets($assets, $optimize_js); - $js_collection_renderer = \Drupal::service('asset.js.collection_renderer'); - $variables['scripts'] = $js_collection_renderer->render($js_assets_header); - $variables['scripts_bottom'] = $js_collection_renderer->render($js_assets_footer); - - // Handle all non-asset attachments. - drupal_process_attached($all_attached); - $variables['head'] = drupal_get_html_head(FALSE); } /** diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponse.php b/core/lib/Drupal/Core/Ajax/AjaxResponse.php index befb3be..c2d8d06 100644 --- a/core/lib/Drupal/Core/Ajax/AjaxResponse.php +++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php @@ -9,6 +9,8 @@ use Drupal\Core\Asset\AttachedAssets; use Drupal\Core\Render\Renderer; +use Drupal\Core\Render\AttachmentsResponseInterface; +use Drupal\Core\Render\AttachmentsResponseTrait; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -19,7 +21,9 @@ * * @ingroup ajax */ -class AjaxResponse extends JsonResponse { +class AjaxResponse extends JsonResponse implements AttachmentsResponseInterface { + + use AttachmentsResponseTrait; /** * The array of ajax commands. @@ -29,32 +33,6 @@ class AjaxResponse extends JsonResponse { protected $commands = array(); /** - * The attachments for this Ajax response. - * - * @var array - */ - protected $attachments = [ - 'library' => [], - 'drupalSettings' => [], - ]; - - /** - * Sets attachments for this Ajax response. - * - * When this Ajax response is rendered, it will take care of generating the - * necessary Ajax commands, if any. - * - * @param array $attachments - * An #attached array. - * - * @return $this - */ - public function setAttachments(array $attachments) { - $this->attachments = $attachments; - return $this; - } - - /** * Add an AJAX command to the response. * * @param \Drupal\Core\Ajax\CommandInterface $command @@ -79,7 +57,7 @@ public function addCommand(CommandInterface $command, $prepend = FALSE) { 'library' => $assets->getLibraries(), 'drupalSettings' => $assets->getSettings(), ]; - $attachments = $this->getRenderer()->mergeAttachments($this->attachments, $attachments); + $attachments = $this->getRenderer()->mergeAttachments($this->getAttachments(), $attachments); $this->setAttachments($attachments); } @@ -171,11 +149,13 @@ protected function ajaxRender(Request $request) { $optimize_css = !defined('MAINTENANCE_MODE') && $config->get('css.preprocess'); $optimize_js = !defined('MAINTENANCE_MODE') && $config->get('js.preprocess'); + $attachments = $this->getAttachments(); + // Resolve the attached libraries into asset collections. $assets = new AttachedAssets(); - $assets->setLibraries(isset($this->attachments['library']) ? $this->attachments['library'] : []) + $assets->setLibraries(isset($attachments['library']) ? $attachments['library'] : []) ->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : []) - ->setSettings(isset($this->attachments['drupalSettings']) ? $this->attachments['drupalSettings'] : []); + ->setSettings(isset($attachments['drupalSettings']) ? $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); diff --git a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php index 883927c..1fb2140 100644 --- a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Render\BareHtmlPageRendererInterface; +use Drupal\Core\Render\HtmlResponse; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Utility\Error; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -130,7 +131,7 @@ protected function onHtml(GetResponseForExceptionEvent $event) { $content = $this->t('The website encountered an unexpected error. Please try again later.'); $output = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $content], $this->t('Error'), 'maintenance_page'); - $response = new Response($output); + $response = new HtmlResponse($output); if ($exception instanceof HttpExceptionInterface) { $response->setStatusCode($exception->getStatusCode()); diff --git a/core/lib/Drupal/Core/EventSubscriber/HtmlResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/HtmlResponseSubscriber.php new file mode 100644 index 0000000..8bb245b --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/HtmlResponseSubscriber.php @@ -0,0 +1,169 @@ +assetResolver = $asset_resolver; + $this->config = $config_factory->get('system.performance'); + $this->cssCollectionRenderer = $css_collection_renderer; + $this->jsCollectionRenderer = $js_collection_renderer; + $this->requestStack = $request_stack; + } + + /** + * Sets extra headers on HtmlResponse responses. + * + * @param Symfony\Component\HttpKernel\Event\FilterResponseEvent $event + * The event to process. + */ + public function onRespond(FilterResponseEvent $event) { + if (!$event->isMasterRequest()) { + return; + } + + $response = $event->getResponse(); + $this->prepareAttachments($response); + } + + public function prepareAttachments($response) { + if ($response instanceof AttachmentsResponseInterface && !($response instanceof AjaxResponse)) { + // Render the attachments into HTML markup + $attached = $response->getAttachments(); + + // 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); + + // 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') && $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); + } + + // 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); + } + + // Print HTML head elements - if present. + if (isset($placeholders['head'])) { + $variables['head'] = drupal_get_html_head(FALSE); + } + + // 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); + } + $response->setContent($content); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + public static function getSubscribedEvents() { + $events[KernelEvents::RESPONSE][] = array('onRespond'); + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php index e43d00e..7ffb3cc 100644 --- a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php @@ -11,6 +11,7 @@ use Drupal\Component\Utility\Xss; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Render\BareHtmlPageRendererInterface; +use Drupal\Core\Render\HtmlResponse; use Drupal\Core\Routing\RouteMatch; use Drupal\Core\Routing\UrlGeneratorInterface; use Drupal\Core\Session\AccountInterface; @@ -18,7 +19,6 @@ use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslationInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -108,7 +108,7 @@ public function onKernelRequestMaintenance(GetResponseEvent $event) { '@site' => $this->config->get('system.site')->get('name'), ))); $output = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $content], $this->t('Site under maintenance'), 'maintenance_page'); - $response = new Response($output, 503); + $response = new HtmlResponse($output, 503); $event->setResponse($response); } else { diff --git a/core/lib/Drupal/Core/Render/AttachmentsResponseInterface.php b/core/lib/Drupal/Core/Render/AttachmentsResponseInterface.php new file mode 100644 index 0000000..cdd50ca --- /dev/null +++ b/core/lib/Drupal/Core/Render/AttachmentsResponseInterface.php @@ -0,0 +1,38 @@ + [], + 'drupalSettings' => [], + 'html_head' => [], + 'feed' => [], + 'html_head_link' => [], + 'http_header' => [], + ]; + + /** + * {@inheritdoc} + */ + public function setAttachments(array $attachments) { + $this->attachments = $attachments; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAttachments() { + return $this->attachments; + } + +} diff --git a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php index c8012c6..b07a29d 100644 --- a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php +++ b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php @@ -7,6 +7,8 @@ namespace Drupal\Core\Render; +use Drupal\Core\EventSubscriber\HtmlResponseSubscriber; + /** * Default bare HTML page renderer. */ @@ -20,13 +22,29 @@ class BareHtmlPageRenderer implements BareHtmlPageRendererInterface { protected $renderer; /** + * The RenderCache service. + * + * @var \Drupal\Core\Render\RenderCache + */ + protected $renderCache; + + /** + * The HtmlResponseSubscriber service. + * + * @var \Drupal\Core\EventSubscriber\HtmlResponseSubscriber + */ + protected $htmlResponseSubscriber; + + /** * Constructs a new BareHtmlPageRenderer. * * @param \Drupal\Core\Render\RendererInterface $renderer * The renderer service. */ - public function __construct(RendererInterface $renderer) { + public function __construct(RendererInterface $renderer, RenderCacheInterface $render_cache, HtmlResponseSubscriber $html_response_subscriber) { $this->renderer = $renderer; + $this->renderCache = $render_cache; + $this->htmlResponseSubscriber = $html_response_subscriber; } /** @@ -55,16 +73,22 @@ public function renderBarePage(array $content, $title, $page_theme_property, arr $html['page']['highlighted'] = ['#type' => 'status_messages']; } - // We must first render the contents of the html.html.twig template, see - // \Drupal\Core\Render\MainContent\HtmlRenderer::renderResponse() for more - // information about this; the exact same pattern is used there and - // explained in detail there. - $this->renderer->render($html['page'], TRUE); - // Add the bare minimum of attachments from the system module and the // current maintenance theme. system_page_attachments($html['page']); - return $this->renderer->render($html); + $this->renderer->renderRoot($html); + $content = $this->renderCache->getCacheableRenderArray($html); + + // Never cache exception pages. + $content['#cache']['max-age'] = 0; + return $content; + } + + /** + * {@inheritdoc} + */ + public function prepareAttachments(AttachmentsResponseInterface $response) { + $this->htmlResponseSubscriber->prepareAttachments($response); } } diff --git a/core/lib/Drupal/Core/Render/HtmlResponse.php b/core/lib/Drupal/Core/Render/HtmlResponse.php new file mode 100644 index 0000000..5a6d6ba --- /dev/null +++ b/core/lib/Drupal/Core/Render/HtmlResponse.php @@ -0,0 +1,47 @@ +addCacheableDependency(CacheableMetadata::createFromRenderArray($content)); + $this->setAttachments($content['#attached']); + $content = $content['#markup']; + } + + parent::setContent($content); + } +} diff --git a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php index addd13c..10f0d08 100644 --- a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php +++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php @@ -13,6 +13,7 @@ use Drupal\Core\Controller\TitleResolverInterface; use Drupal\Core\Display\PageVariantInterface; use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Render\HtmlResponse; use Drupal\Core\Render\PageDisplayVariantSelectionEvent; use Drupal\Core\Render\RenderCacheInterface; use Drupal\Core\Render\RendererInterface; @@ -119,45 +120,22 @@ 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); - // The three parts of rendered markup in html.html.twig (page_top, page and - // page_bottom) must be rendered with drupal_render_root(), so that their - // #post_render_cache callbacks are executed (which may attach additional - // assets). - // html.html.twig must be able to render the final list of attached assets, - // and hence may not execute any #post_render_cache_callbacks (because they - // might add yet more assets to be attached), and therefore it must be - // rendered with drupal_render(), not drupal_render_root(). - $this->renderer->render($html['page'], TRUE); - if (isset($html['page_top'])) { - $this->renderer->render($html['page_top'], TRUE); - } - if (isset($html['page_bottom'])) { - $this->renderer->render($html['page_bottom'], TRUE); - } - $content = $this->renderer->render($html); + // @todo Make renderRoot return a cacheable render array directly. + $this->renderer->renderRoot($html); + $content = $this->renderCache->getCacheableRenderArray($html); + + // Also associate the "rendered" cache tag. This allows us to invalidate the + // entire render cache, regardless of the cache bin. + $content['#cache']['tags'][] = 'rendered'; // Set the generator in the HTTP header. list($version) = explode('.', \Drupal::VERSION, 2); - $response = new CacheableResponse($content, 200,[ + $response = new HtmlResponse($content, 200,[ 'Content-Type' => 'text/html; charset=UTF-8', 'X-Generator' => 'Drupal ' . $version . ' (https://www.drupal.org)' ]); - // Bubble the cacheability metadata associated with the rendered render - // arrays to the response. - foreach (['page_top', 'page', 'page_bottom'] as $region) { - if (isset($html[$region])) { - $response->addCacheableDependency(CacheableMetadata::createFromRenderArray($html[$region])); - } - } - - // Also associate the "rendered" cache tag. This allows us to invalidate the - // entire render cache, regardless of the cache bin. - $default = new CacheableMetadata(); - $default->setCacheTags(['rendered']); - $response->addCacheableDependency($default); - return $response; } diff --git a/core/modules/system/src/Controller/DbUpdateController.php b/core/modules/system/src/Controller/DbUpdateController.php index 3c10c8a..638d6e9 100644 --- a/core/modules/system/src/Controller/DbUpdateController.php +++ b/core/modules/system/src/Controller/DbUpdateController.php @@ -13,6 +13,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface; use Drupal\Core\Render\BareHtmlPageRendererInterface; +use Drupal\Core\Render\HtmlResponse; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Site\Settings; use Drupal\Core\State\StateInterface; @@ -198,7 +199,7 @@ public function handle($op, Request $request) { } $title = isset($output['#title']) ? $output['#title'] : $this->t('Drupal database update'); - return new Response($this->bareHtmlPageRenderer->renderBarePage($output, $title, 'maintenance_page', $regions)); + return new HtmlResponse($this->bareHtmlPageRenderer->renderBarePage($output, $title, 'maintenance_page', $regions)); } /** diff --git a/core/modules/views/views.module b/core/modules/views/views.module index 39589c2..cfd2ab9 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -288,29 +288,6 @@ function views_theme_suggestions_container_alter(array &$suggestions, array $var } /** - * Implements hook_element_info_alter(). - * - * @see views_page_display_pre_render() - * @see views_preprocess_page() - */ -function views_element_info_alter(&$types) { - $types['page']['#pre_render'][] = 'views_page_display_pre_render'; -} - -/** - * #pre_render callback to set contextual links for views using a Page display. - */ -function views_page_display_pre_render(array $element) { - // 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. - if ($view = views_get_page_view()) { - views_add_contextual_links($element, 'page', $view, $view->current_display); - } - return $element; -} - -/** * Implements MODULE_preprocess_HOOK(). */ function views_preprocess_html(&$variables) { @@ -319,6 +296,13 @@ function views_preprocess_html(&$variables) { 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. + if ($view = views_get_page_view()) { + views_add_contextual_links($variables['page'], 'page', $view, $view->current_display); + } + // 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(). diff --git a/core/themes/engines/twig/twig.engine b/core/themes/engines/twig/twig.engine index 9f8bc4a..38f3d5f 100644 --- a/core/themes/engines/twig/twig.engine +++ b/core/themes/engines/twig/twig.engine @@ -62,6 +62,17 @@ function twig_render_template($template_file, array $variables) { catch (\Twig_Error_Loader $e) { drupal_set_message($e->getMessage(), 'error'); } + catch (\Twig_Error_Runtime $e) { + // In case there is a previous exception, we just display the message and + // show the original Exception so that the original function that fails is + // shown. + $previous_exception = $e->getPrevious(); + if ($previous_exception) { + drupal_set_message($e->getMessage(), 'error'); + throw $previous_exception; + } + throw $e; + } if ($twig_service->isDebug()) { $output['debug_prefix'] .= "\n\n"; $output['debug_prefix'] .= "\n";