core/authorize.php | 3 +- core/core.services.yml | 58 +++-- core/includes/batch.inc | 3 +- core/includes/common.inc | 141 ----------- core/includes/errors.inc | 3 +- core/includes/install.core.inc | 3 +- core/includes/menu.inc | 10 +- core/includes/theme.inc | 91 +++---- core/lib/Drupal/Core/Ajax/AjaxResponseRenderer.php | 93 ------- .../Core/Block/MainContentBlockPluginInterface.php | 25 ++ core/lib/Drupal/Core/ContentNegotiation.php | 1 + core/lib/Drupal/Core/Controller/AjaxController.php | 104 ++++---- .../Drupal/Core/Controller/DialogController.php | 142 +++-------- core/lib/Drupal/Core/Controller/HtmlController.php | 277 +++++++++++++++++++++ .../Drupal/Core/Controller/HtmlControllerBase.php | 79 ------ .../Drupal/Core/Controller/HtmlPageController.php | 81 ------ .../Core/Controller/MainContentControllerBase.php | 87 +++++++ .../Controller/MainContentControllerInterface.php | 92 +++++++ .../lib/Drupal/Core/Controller/ModalController.php | 30 +++ core/lib/Drupal/Core/CoreServiceProvider.php | 3 + .../Core/Display/Annotation/DisplayVariant.php | 11 +- .../Core/Display/Annotation/PageDisplayVariant.php | 26 ++ .../Drupal/Core/Display/PageVariantInterface.php | 30 +++ .../ContentControllerSubscriber.php | 62 +++-- .../CustomPageExceptionHtmlSubscriber.php | 74 ++---- .../DefaultExceptionHtmlSubscriber.php | 90 +++---- .../EventSubscriber/DefaultExceptionSubscriber.php | 59 +---- .../Core/EventSubscriber/HtmlViewSubscriber.php | 110 -------- .../EventSubscriber/MaintenanceModeSubscriber.php | 21 +- .../Drupal/Core/EventSubscriber/ViewSubscriber.php | 40 +-- .../Core/Page/DefaultHtmlFragmentRenderer.php | 155 ------------ .../Drupal/Core/Page/DefaultHtmlPageRenderer.php | 125 ---------- core/lib/Drupal/Core/Page/FeedLinkElement.php | 32 --- core/lib/Drupal/Core/Page/HeadElement.php | 100 -------- core/lib/Drupal/Core/Page/HtmlFragment.php | 229 ----------------- .../lib/Drupal/Core/Page/HtmlFragmentInterface.php | 66 ----- .../Core/Page/HtmlFragmentRendererInterface.php | 38 --- core/lib/Drupal/Core/Page/HtmlPage.php | 234 ----------------- .../Drupal/Core/Page/HtmlPageRendererInterface.php | 29 --- core/lib/Drupal/Core/Page/LinkElement.php | 40 --- core/lib/Drupal/Core/Page/MetaElement.php | 81 ------ core/lib/Drupal/Core/Page/RenderHtmlRenderer.php | 88 ------- .../Core/Page/RenderHtmlRendererInterface.php | 31 --- .../Drupal/Core/Render/BareHtmlPageRenderer.php | 78 ++++++ .../Core/Render/BareHtmlPageRendererInterface.php | 72 ++++++ core/lib/Drupal/Core/Render/Element/Html.php | 107 +++++++- core/lib/Drupal/Core/Render/Element/Page.php | 6 +- .../Core/Render/MainContentControllerPass.php | 33 +++ .../src/Authentication/Provider/BasicAuth.php | 2 +- core/modules/block/block.module | 35 +-- core/modules/block/block.services.yml | 5 + .../BlockPageDisplayVariantSubscriber.php | 64 +++++ .../{FullPageVariant.php => BlockPageVariant.php} | 45 +++- .../Plugin/DisplayVariant/DemoBlockPageVariant.php | 134 ++++++++++ core/modules/block/src/Tests/BlockTest.php | 5 +- ...ageVariantTest.php => BlockPageVariantTest.php} | 115 ++++++--- .../modules/node/src/Tests/Views/FrontPageTest.php | 2 +- core/modules/rest/src/Tests/DeleteTest.php | 2 +- core/modules/simpletest/simpletest.module | 1 - .../system/src/Controller/BatchController.php | 92 +------ .../system/src/Controller/DbUpdateController.php | 19 +- .../system/src/Controller/Http4xxController.php | 41 +++ .../src/Event/PageDisplayVariantSelectionEvent.php | 55 ++++ core/modules/system/src/Event/SystemEvents.php | 23 ++ .../system/src/Plugin/Block/SystemMainBlock.php | 21 +- .../Plugin/DisplayVariant/SimplePageVariant.php | 47 ++++ .../system/src/Tests/Common/AddFeedTest.php | 18 +- .../system/src/Tests/Common/PageRenderTest.php | 7 +- .../src/Tests/Routing/ExceptionHandlingTest.php | 27 +- .../src/Tests/System/MainContentFallbackTest.php | 85 ------- core/modules/system/system.routing.yml | 20 +- core/modules/system/templates/html.html.twig | 14 +- .../modules/ajax_forms_test/ajax_forms_test.module | 4 +- .../src/Controller/EntityTestController.php | 2 +- .../src/Controller/ErrorTestController.php | 2 +- .../tests/modules/menu_test/menu_test.module | 4 +- .../modules/menu_test/src/TestControllers.php | 12 +- .../src/Controller/ModuleTestController.php | 2 +- .../paramconverter_test/src/TestControllers.php | 6 +- .../router_test_directory/src/TestContent.php | 6 +- .../router_test_directory/src/TestControllers.php | 8 +- .../src/Controller/SessionTestController.php | 16 +- .../src/EventSubscriber/HtmlPageSubscriber.php | 45 ---- .../system_module_test/system_module_test.module | 20 ++ .../system_module_test.services.yml | 5 - .../src/Controller/SystemTestController.php | 8 +- .../tests/modules/system_test/system_test.module | 19 +- .../modules/theme_test/src/ThemeTestController.php | 6 +- .../src/TwigThemeTestController.php | 2 +- core/modules/system/theme.api.php | 61 +++++ core/modules/views/views.module | 24 +- .../Tests/Core/Ajax/AjaxResponseRendererTest.php | 132 ---------- .../Tests/Core/Controller/AjaxControllerTest.php | 118 +++++++++ core/tests/Drupal/Tests/Core/Page/HtmlPageTest.php | 62 ----- core/themes/bartik/bartik.theme | 56 ++--- core/themes/seven/css/theme/install-page.css | 2 +- core/themes/seven/css/theme/maintenance-page.css | 2 +- core/themes/seven/seven.theme | 55 ++-- 98 files changed, 2029 insertions(+), 2922 deletions(-) diff --git a/core/authorize.php b/core/authorize.php index 4b0f249..22bede6 100644 --- a/core/authorize.php +++ b/core/authorize.php @@ -25,7 +25,6 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Drupal\Core\Site\Settings; -use Drupal\Core\Page\DefaultHtmlPageRenderer; // Change the directory to the Drupal root. chdir('..'); @@ -150,7 +149,7 @@ function authorize_access_allowed() { if (!empty($output)) { $response->headers->set('Content-Type', 'text/html; charset=utf-8'); - $response->setContent(DefaultHtmlPageRenderer::renderPage($output, $page_title, 'maintenance', array( + $response->setContent(\Drupal::service('bare_html_page_renderer')->renderMaintenancePage($output, $page_title, array( '#show_messages' => $show_messages, ))); $response->send(); diff --git a/core/core.services.yml b/core/core.services.yml index 7f9ed9b..298dedd 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -633,7 +633,7 @@ services: - { name: route_enhancer, priority: 20 } route_content_controller_subscriber: class: Drupal\Core\EventSubscriber\ContentControllerSubscriber - arguments: ['@content_negotiation'] + arguments: ['@content_negotiation', '%main_content_controllers%'] tags: - { name: event_subscriber } route_content_form_controller_subscriber: @@ -650,17 +650,32 @@ services: class: Drupal\Core\EventSubscriber\RouteMethodSubscriber tags: - { name: event_subscriber } - controller.page: - class: Drupal\Core\Controller\HtmlPageController - arguments: ['@controller_resolver', '@title_resolver', '@render_html_renderer'] - controller.ajax: + main_content_controller.base: + abstract: true + arguments: ['@controller_resolver'] + main_content_controller.html: + class: Drupal\Core\Controller\HtmlController + parent: main_content_controller.base + arguments: ['@title_resolver', '@plugin.manager.display_variant', '@event_dispatcher'] + tags: + - { name: main_content_controller, format: html } + main_content_controller.ajax: class: Drupal\Core\Controller\AjaxController - arguments: ['@controller_resolver', '@ajax_response_renderer'] - controller.dialog: + parent: main_content_controller.base + arguments: ['@element_info'] + tags: + - { name: main_content_controller, format: drupal_ajax } + main_content_controller.dialog: class: Drupal\Core\Controller\DialogController - arguments: ['@controller_resolver', '@title_resolver'] - ajax_response_renderer: - class: Drupal\Core\Ajax\AjaxResponseRenderer + parent: main_content_controller.base + arguments: ['@title_resolver'] + tags: + - { name: main_content_controller, format: drupal_dialog } + main_content_controller.modal: + class: Drupal\Core\Controller\ModalController + parent: main_content_controller.dialog + tags: + - { name: main_content_controller, format: drupal_modal } router_listener: class: Symfony\Component\HttpKernel\EventListener\RouterListener tags: @@ -672,20 +687,9 @@ services: class: Drupal\Core\EventSubscriber\ViewSubscriber tags: - { name: event_subscriber } - arguments: ['@content_negotiation', '@title_resolver', '@ajax_response_renderer'] - html_view_subscriber: - class: Drupal\Core\EventSubscriber\HtmlViewSubscriber - tags: - - { name: event_subscriber } - arguments: ['@html_fragment_renderer', '@html_page_renderer'] - render_html_renderer: - class: Drupal\Core\Page\RenderHtmlRenderer - arguments: ['@url_generator'] - html_fragment_renderer: - class: Drupal\Core\Page\DefaultHtmlFragmentRenderer - arguments: ['@language_manager'] - html_page_renderer: - class: Drupal\Core\Page\DefaultHtmlPageRenderer + arguments: ['@content_negotiation', '@title_resolver'] + bare_html_page_renderer: + class: Drupal\Core\Render\BareHtmlPageRenderer private_key: class: Drupal\Core\PrivateKey arguments: ['@state'] @@ -734,7 +738,7 @@ services: arguments: ['@state', '@current_user'] maintenance_mode_subscriber: class: Drupal\Core\EventSubscriber\MaintenanceModeSubscriber - arguments: ['@maintenance_mode', '@config.factory', '@string_translation', '@url_generator', '@current_user'] + arguments: ['@maintenance_mode', '@config.factory', '@string_translation', '@url_generator', '@current_user', '@bare_html_page_renderer'] tags: - { name: event_subscriber } path_subscriber: @@ -774,12 +778,12 @@ services: class: Drupal\Core\EventSubscriber\DefaultExceptionHtmlSubscriber tags: - { name: event_subscriber } - arguments: ['@html_fragment_renderer', '@html_page_renderer'] + arguments: ['@http_kernel'] exception.default: class: Drupal\Core\EventSubscriber\DefaultExceptionSubscriber tags: - { name: event_subscriber } - arguments: ['@html_fragment_renderer', '@html_page_renderer', '@config.factory'] + arguments: ['@config.factory', '@bare_html_page_renderer'] exception.logger: class: Drupal\Core\EventSubscriber\ExceptionLoggingSubscriber tags: diff --git a/core/includes/batch.inc b/core/includes/batch.inc index 68fc14b..1ba845f 100644 --- a/core/includes/batch.inc +++ b/core/includes/batch.inc @@ -19,7 +19,6 @@ use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Batch\Percentage; use Drupal\Core\Form\FormState; -use Drupal\Core\Page\DefaultHtmlPageRenderer; use Drupal\Core\Url; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -136,7 +135,7 @@ 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 = DefaultHtmlPageRenderer::renderPage($fallback, $current_set['title'], 'maintenance', array( + $fallback = \Drupal::service('bare_html_page_renderer')->renderMaintenancePage($fallback, $current_set['title'], array( '#show_messages' => FALSE, )); list($fallback) = explode('', $fallback); diff --git a/core/includes/common.inc b/core/includes/common.inc index 020d84d..2fc5221 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -1117,9 +1117,6 @@ function drupal_get_css($css = NULL, $skip_alter = FALSE, $theme_add_css = TRUE) '#type' => 'styles', '#items' => $css, ); - if (!empty($setting)) { - $styles['#attached']['js'][] = array('type' => 'setting', 'data' => $setting); - } return drupal_render($styles); } @@ -2262,39 +2259,6 @@ function drupal_page_set_cache(Response $response, Request $request) { } /** - * Sets the main page content value for later use. - * - * Given the nature of the Drupal page handling, this will be called once with - * a string or array. We store that and return it later as the block is being - * displayed. - * - * @param $content - * A string or renderable array representing the body of the page. - * - * @return - * If called without $content, a renderable array representing the body of - * the page. - */ -function drupal_set_page_content($content = NULL) { - $content_block = &drupal_static(__FUNCTION__, NULL); - $main_content_display = &drupal_static('system_main_content_added', FALSE); - - // Filter out each empty value, though allow '0' and 0, which would be - // filtered out by empty(). - if ($content !== NULL && $content !== '') { - $content_block = (is_array($content) ? $content : array('main' => array('#markup' => $content))); - } - else { - // Indicate that the main content has been requested. We assume that - // the module requesting the content will be adding it to the page. - // A module can indicate that it does not handle the content by setting - // the static variable back to FALSE after calling this function. - $main_content_display = TRUE; - return $content_block; - } -} - -/** * Pre-render callback: Renders a link into #markup. * * @deprecated Use \Drupal\Core\Render\Element\Link::preRenderLink(). @@ -2399,111 +2363,6 @@ function drupal_pre_render_links($element) { } /** - * Processes the page render array, enhancing it as necessary. - * - * @param $page - * A string or array representing the content of a page. The array consists of - * the following keys: - * - #type: Value is always 'page'. This pushes the theming through - * the page template (required). - * - #show_messages: Suppress drupal_get_message() items. Used by Batch - * API (optional). - * - * @return array - * The processed render array for the page. - * - * @see hook_page_attachments() - * @see hook_page_attachments_alter() - * @see hook_page_top() - * @see hook_page_bottom() - * @see element_info() - */ -function drupal_prepare_page($page) { - $main_content_display = &drupal_static('system_main_content_added', FALSE); - - // Pull out the page title to set it back later. - if (is_array($page) && isset($page['#title'])) { - $title = $page['#title']; - } - - // Allow menu callbacks to return strings or arbitrary arrays to render. - // If the array returned is not of #type page directly, we need to fill - // in the page with defaults. - if (is_string($page) || (is_array($page) && (!isset($page['#type']) || ($page['#type'] != 'page')))) { - drupal_set_page_content($page); - $page = element_info('page'); - } - - // Modules can add attachments. - $attachments = []; - foreach (\Drupal::moduleHandler()->getImplementations('page_attachments') as $module) { - $function = $module . '_page_attachments'; - $function($attachments); - } - if (array_diff(array_keys($attachments), ['#attached', '#post_render_cache']) !== []) { - throw new \LogicException('Only #attached and #post_render_cache may be set in hook_page_attachments().'); - } - // Modules and themes can alter page attachments. - \Drupal::moduleHandler()->alter('page_attachments', $attachments); - \Drupal::theme()->alter('page_attachments', $attachments); - if (array_diff(array_keys($attachments), ['#attached', '#post_render_cache']) !== []) { - throw new \LogicException('Only #attached and #post_render_cache may be set in hook_page_attachments_alter().'); - } - if (isset($attachments['#attached'])) { - $page['#attached'] = $attachments['#attached']; - } - if (isset($attachments['#post_render_cache'])) { - $page['#post_render_cache'] = $attachments['#post_render_cache']; - } - - // Modules can add renderable arrays to the top and bottom of the page. - $pseudo_page_top = []; - $pseudo_page_bottom = []; - foreach (\Drupal::moduleHandler()->getImplementations('page_top') as $module) { - $function = $module . '_page_top'; - $function($pseudo_page_top); - } - foreach (\Drupal::moduleHandler()->getImplementations('page_bottom') as $module) { - $function = $module . '_page_bottom'; - $function($pseudo_page_bottom); - } - if (!empty($pseudo_page_top)) { - $page['page_top'] = $pseudo_page_top; - } - if (!empty($pseudo_page_bottom)) { - $page['page_bottom'] = $pseudo_page_bottom; - } - - // @todo Clean this up as part of https://www.drupal.org/node/2352155. - if (\Drupal::moduleHandler()->moduleExists('block')) { - _block_page_build($page); - // Find all non-empty page regions, and add a theme wrapper function that - // allows them to be consistently themed. - $regions = system_region_list(\Drupal::theme()->getActiveTheme()->getName()); - foreach (array_keys($regions) as $region) { - if (!empty($page[$region])) { - $page[$region]['#theme_wrappers'][] = 'region'; - $page[$region]['#region'] = $region; - } - } - } - - // If no module has taken care of the main content, add it to the page now. - // This allows the site to still be usable even if no modules that - // control page regions (for example, the Block module) are enabled. - if (!$main_content_display) { - $page['content']['system_main'] = drupal_set_page_content(); - } - - // Set back the previously stored title. - if (isset($title)) { - $page['#title'] = $title; - } - - return $page; -} - -/** * Renders final HTML given a structured array tree. * * Calls drupal_render() in such a way that #post_render_cache callbacks are diff --git a/core/includes/errors.inc b/core/includes/errors.inc index 4065408..be30937 100644 --- a/core/includes/errors.inc +++ b/core/includes/errors.inc @@ -8,7 +8,6 @@ use Drupal\Component\Utility\SafeMarkup; use Drupal\Component\Utility\Xss; use Drupal\Core\Logger\RfcLogLevel; -use Drupal\Core\Page\DefaultHtmlPageRenderer; use Drupal\Core\Utility\Error; use Symfony\Component\HttpFoundation\Response; @@ -236,7 +235,7 @@ function _drupal_log_error($error, $fatal = FALSE) { install_display_output($output, $GLOBALS['install_state']); } else { - $output = DefaultHtmlPageRenderer::renderPage($message, 'Error'); + $output = \Drupal::service('bare_html_page_renderer')->renderMaintenancePage($message, 'Error'); } $response = new Response($output, 500); diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index e494862..7bdf888 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -14,7 +14,6 @@ use Drupal\Core\Installer\InstallerKernel; use Drupal\Core\Language\Language; use Drupal\Core\Language\LanguageManager; -use Drupal\Core\Page\DefaultHtmlPageRenderer; use Drupal\Core\Site\Settings; use Drupal\Core\StringTranslation\Translator\FileTranslation; use Drupal\Core\Extension\ExtensionDiscovery; @@ -934,7 +933,7 @@ function install_display_output($output, $install_state) { 'ETag' => '"' . REQUEST_TIME . '"', ); $response->headers->add($default_headers); - $response->setContent(DefaultHtmlPageRenderer::renderPage($output, $output['#title'], 'install', $regions)); + $response->setContent(\Drupal::service('bare_html_page_renderer')->renderInstallPage($output, $output['#title'], $regions)); $response->send(); exit; } diff --git a/core/includes/menu.inc b/core/includes/menu.inc index 76aafd0..b42ff1c 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -110,13 +110,15 @@ * class and method. Page controller classes do not necessarily need to * implement any particular interface or extend any particular base class. The * only requirement is that the method specified in your *.routing.yml file - * return one of the following, depending on whether you specified _content or - * _controller in the routing file defaults section: + * returns: * - A render array (see the * @link theme_render Theme and render topic @endlink for more information), * if _content is used in the routing file. - * - A \Drupal\Core\Page\HtmlFragmentInterface object (fragment or page), if - * _content is used in the routing file. + * This render array is then rendered in the requested format (HTML, dialog, + * modal, AJAX are supported by default). In the case of HTML, it will be + * surrounded by blocks by default: the Block module is enabled by default, + * and hence its Page Display Variant that surrounds the main content with + * blocks is also used by default. * - A \Symfony\Component\HttpFoundation\Response object, if _controller is * used in the routing file. * As a note, if your module registers multiple simple routes, it is usual diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 15d62a8..944dc9e 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -17,8 +17,6 @@ use Drupal\Core\Config\StorageException; use Drupal\Core\Extension\Extension; use Drupal\Core\Extension\ExtensionNameLengthException; -use Drupal\Core\Page\LinkElement; -use Drupal\Core\Page\MetaElement; use Drupal\Core\Template\Attribute; use Drupal\Core\Theme\ThemeSettings; use Drupal\Component\Utility\NestedArray; @@ -1645,22 +1643,24 @@ function drupal_pre_render_html(array $element) { * - page: A render element representing the page. */ function template_preprocess_html(&$variables) { - /** @var $page \Drupal\Core\Page\HtmlPage */ - $page = $variables['page_object']; + $variables['html_attributes'] = new Attribute(); - $variables['html_attributes'] = $page->getHtmlAttributes(); - $variables['attributes'] = $page->getBodyAttributes(); - $variables['page'] = $page; + // HTML element attributes. + $language_interface = \Drupal::languageManager()->getCurrentLanguage(); + $variables['html_attributes']['lang'] = $language_interface->getId(); + $variables['html_attributes']['dir'] = $language_interface->getDirection(); // Compile a list of classes that are going to be applied to the body element. // This allows advanced theming based on context (home page, node of certain // type, etc.). - $body_classes = $variables['attributes']['class']; + if (isset($variables['db_is_active']) && !$variables['db_is_active']) { + $variables['attributes']['class'][] = 'db-offline'; + } // Add a class that tells us whether the page is viewed by an authenticated // user. if ($variables['logged_in']) { - $body_classes[] = 'user-logged-in'; + $variables['attributes']['class'][] = 'user-logged-in'; } // Add a class that tells us what path the page is located make it possible // to theme the page depending on the current path (e.g. node, admin, user, @@ -1668,20 +1668,18 @@ function template_preprocess_html(&$variables) { $path = \Drupal::request()->getPathInfo(); if (drupal_is_front_page()) { - $body_classes[] = 'path-frontpage'; + $variables['attributes']['class'][] = 'path-frontpage'; } else { $segment = explode('/', $path); - $body_classes[] = 'path-' . drupal_html_class($segment[1]); + $variables['attributes']['class'][] = 'path-' . drupal_html_class($segment[1]); } - $variables['attributes']['class'] = $body_classes; - $site_config = \Drupal::config('system.site'); // Construct page title. - if ($page->hasTitle()) { + if (!empty($variables['html']['page']['#title'])) { $head_title = array( - 'title' => SafeMarkup::set(trim(strip_tags($page->getTitle()))), + 'title' => SafeMarkup::set(trim(strip_tags($variables['html']['page']['#title']))), 'name' => String::checkPlain($site_config->get('name')), ); } @@ -1709,35 +1707,27 @@ function template_preprocess_html(&$variables) { } $variables['head_title'] = SafeMarkup::set($output); - // @todo Remove drupal_*_html_head() and refactor accordingly. - $html_heads = drupal_get_html_head(FALSE); - uasort($html_heads, 'Drupal\Component\Utility\SortArray::sortByWeightElement'); - foreach ($html_heads as $name => $tag) { - if ($tag['#tag'] == 'link') { - $link = new LinkElement($name, isset($tag['#attributes']['content']) ? $tag['#attributes']['content'] : NULL, $tag['#attributes']); - if (!empty($tag['#noscript'])) { - $link->setNoScript(); - } - $page->addLinkElement($link); - } - elseif ($tag['#tag'] == 'meta') { - $metatag = new MetaElement(NULL, $tag['#attributes']); - if (!empty($tag['#noscript'])) { - $metatag->setNoScript(); - } - $page->addMetaElement($metatag); - } + // 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['html']['page']['#attached']); + if (isset($variables['html']['page_top'])) { + $attached = drupal_merge_attached($attached, $variables['html']['page_top']['#attached']); } - - // Add favicon. - if (theme_get_setting('features.favicon')) { - $url = UrlHelper::stripDangerousProtocols(theme_get_setting('favicon.url')); - $link = new LinkElement($url, 'shortcut icon', ['type' => theme_get_setting('favicon.mimetype')]); - $page->addLinkElement($link); + if (isset($variables['html']['page_bottom'])) { + $attached = drupal_merge_attached($attached, $variables['html']['page_bottom']['#attached']); } - $variables['page_top'][] = array('#markup' => $page->getBodyTop()); - $variables['page_bottom'][] = array('#markup' => $page->getBodyBottom()); + // Render the attachments into HTML markup to be used directly in the template + // for #type => html: html.html.twig. + $all_attached = ['#attached' => $attached]; + drupal_process_attached($all_attached); + + $variables['html']['styles'] = drupal_get_css(); + $variables['html']['scripts'] = drupal_get_js(); + $variables['html']['scripts_bottom'] = drupal_get_js('footer'); + $variables['html']['head'] = drupal_get_html_head(FALSE); } /** @@ -1748,8 +1738,6 @@ function template_preprocess_html(&$variables) { * Most themes use their own copy of page.html.twig. The default is located * inside "modules/system/page.html.twig". Look in there for the full list of * variables. - * - * @see DefaultHtmlFragmentRenderer::render() */ function template_preprocess_page(&$variables) { $language_interface = \Drupal::languageManager()->getCurrentLanguage(); @@ -1889,15 +1877,6 @@ function template_preprocess_maintenance_page(&$variables) { // @todo Rename the templates to page--maintenance + page--install. template_preprocess_page($variables); - $page_object = $variables['page']['#page']; - $attributes = $page_object->getBodyAttributes(); - $classes = $attributes['class']; - $classes[] = 'maintenance-page'; - if (isset($variables['db_is_active']) && !$variables['db_is_active']) { - $classes[] = 'db-offline'; - } - $attributes['class'] = $classes; - // @see system_page_attachments() $variables['#attached']['library'][] = 'core/normalize'; $variables['#attached']['library'][] = 'system/maintenance'; @@ -1917,12 +1896,6 @@ function template_preprocess_maintenance_page(&$variables) { function template_preprocess_install_page(&$variables) { template_preprocess_maintenance_page($variables); - $page_object = $variables['page']['#page']; - $attributes = $page_object->getBodyAttributes(); - $classes = $attributes['class']; - $classes[] = 'install-page'; - $attributes['class'] = $classes; - // Override the site name that is displayed on the page, since Drupal is // still in the process of being installed. $distribution_name = String::checkPlain(drupal_install_profile_distribution_name()); @@ -2160,7 +2133,7 @@ function drupal_common_theme() { return array( // From theme.inc. 'html' => array( - 'variables' => array('page_object' => NULL), + 'render element' => 'html', ), 'page' => array( 'render element' => 'page', diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponseRenderer.php b/core/lib/Drupal/Core/Ajax/AjaxResponseRenderer.php deleted file mode 100644 index ab9aa8e..0000000 diff --git a/core/lib/Drupal/Core/Block/MainContentBlockPluginInterface.php b/core/lib/Drupal/Core/Block/MainContentBlockPluginInterface.php new file mode 100644 index 0000000..55fc292 --- /dev/null +++ b/core/lib/Drupal/Core/Block/MainContentBlockPluginInterface.php @@ -0,0 +1,25 @@ +controllerResolver = $controller_resolver; - $this->ajaxRenderer = $ajax_renderer; + public function __construct(ControllerResolverInterface $controller_resolver, ElementInfoManagerInterface $element_info_manager) { + parent::__construct($controller_resolver); + $this->elementInfoManager = $element_info_manager; } /** - * Controller method for Ajax content. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. - * @param callable $_content - * The callable that returns the content of the Ajax response. - * - * @return \Drupal\Core\Ajax\AjaxResponse - * A response object. + * {@inheritdoc} */ - public function content(Request $request, $_content) { - $content = $this->getContentResult($request, $_content); - return $this->ajaxRenderer->render($content); + public function renderContentIntoResponse(array $main_content, $title, array $custom) { + $response = new AjaxResponse(); + + if (isset($main_content['#type']) && ($main_content['#type'] == 'ajax')) { + // Complex Ajax callbacks can return a result that contains an error + // message or a specific set of commands to send to the browser. + $main_content += $this->elementInfoManager->getInfo('ajax'); + $error = $main_content['#error']; + if (!empty($error)) { + // Fall back to some default message otherwise use the specific one. + if (!is_string($error)) { + $error = 'An error occurred while handling the request: The server received invalid input.'; + } + $response->addCommand(new AlertCommand($error)); + } + } + + $html = $this->drupalRender($main_content); + + // The selector for the insert command is NULL as the new content will + // replace the element making the Ajax call. The default 'replaceWith' + // behavior can be changed with #ajax['method']. + $response->addCommand(new InsertCommand(NULL, $html)); + $status_messages = array('#theme' => 'status_messages'); + $output = $this->drupalRender($status_messages); + if (!empty($output)) { + $response->addCommand(new PrependCommand(NULL, $output)); + } + return $response; } /** - * Returns the result of invoking the sub-controller. + * Wraps drupal_render(). * - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. - * @param mixed $controller_definition - * A controller definition string, or a callable object/closure. - * - * @return mixed - * The result of invoking the controller. Render arrays, strings, HtmlPage, - * and HtmlFragment objects are possible. + * @todo: Remove as part of https://drupal.org/node/2182149 */ - public function getContentResult(Request $request, $controller_definition) { - if ($controller_definition instanceof \Closure) { - $callable = $controller_definition; - } - else { - $callable = $this->controllerResolver->getControllerFromDefinition($controller_definition); - } - $arguments = $this->controllerResolver->getArguments($request, $callable); - $page_content = call_user_func_array($callable, $arguments); - - return $page_content; + protected function drupalRender(&$elements, $is_root_call = FALSE) { + $output = drupal_render($elements, $is_root_call); + drupal_process_attached($elements); + return $output; } } diff --git a/core/lib/Drupal/Core/Controller/DialogController.php b/core/lib/Drupal/Core/Controller/DialogController.php index 6aef7fa..655354d 100644 --- a/core/lib/Drupal/Core/Controller/DialogController.php +++ b/core/lib/Drupal/Core/Controller/DialogController.php @@ -9,22 +9,13 @@ use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Ajax\OpenDialogCommand; -use Drupal\Core\Page\HtmlPage; use Drupal\Core\Routing\RouteMatchInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; /** - * Defines a default controller for dialog requests. + * Default controller for dialog requests. */ -class DialogController { - - /** - * The controller resolver service. - * - * @var \Drupal\Core\Controller\ControllerResolverInterface - */ - protected $controllerResolver; +class DialogController extends MainContentControllerBase { /** * The title resolver. @@ -42,116 +33,53 @@ class DialogController { * The title resolver. */ public function __construct(ControllerResolverInterface $controller_resolver, TitleResolverInterface $title_resolver) { - $this->controllerResolver = $controller_resolver; + parent::__construct($controller_resolver); $this->titleResolver = $title_resolver; } /** - * Displays content in a modal dialog. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. - * @param \Drupal\Core\Routing\RouteMatchInterface $route_match - * The route match. - * @param mixed $_content - * A controller definition string, or a callable object/closure. - * - * @return \Drupal\Core\Ajax\AjaxResponse - * AjaxResponse to return the content wrapper in a modal dialog. - */ - public function modal(Request $request, RouteMatchInterface $route_match, $_content) { - return $this->dialog($request, $route_match, $_content, TRUE); - } - - /** - * Displays content in a dialog. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. - * @param \Drupal\Core\Routing\RouteMatchInterface $route_match - * The route match. - * @param mixed $_content - * A controller definition string, or a callable object/closure. - * @param bool $modal - * (optional) TRUE to render a modal dialog. Defaults to FALSE. - * - * @return \Drupal\Core\Ajax\AjaxResponse - * AjaxResponse to return the content wrapper in a dialog. + * {@inheritdoc} */ - public function dialog(Request $request, RouteMatchInterface $route_match, $_content, $modal = FALSE) { - $page_content = $this->getContentResult($request, $_content); - - // Allow controllers to return a HtmlPage or a Response object directly. - if ($page_content instanceof HtmlPage) { - $page_content = $page_content->getContent(); - } - if ($page_content instanceof Response) { - $page_content = $page_content->getContent(); - } - - // Most controllers return a render array, but some return a string. - if (!is_array($page_content)) { - $page_content = array( - '#markup' => $page_content, - ); - } - - $content = drupal_render_root($page_content); - drupal_process_attached($page_content); - $title = isset($page_content['#title']) ? $page_content['#title'] : $this->titleResolver->getTitle($request, $route_match->getRouteObject()); - $response = new AjaxResponse(); - // Fetch any modal options passed in from data-dialog-options. + public function prepareContent(array $main_content, Request $request, RouteMatchInterface $route_match) { + // Determine the dialog options and the target for the OpenDialogCommand. $options = $request->request->get('dialogOptions', array()); - // Set modal flag and re-use the modal ID. - if ($modal) { - $options['modal'] = TRUE; - $target = '#drupal-modal'; + // Generate the target wrapper for the dialog. + if (isset($options['target'])) { + // If the target was nominated in the incoming options, use that. + $target = $options['target']; + // Ensure the target includes the #. + if (substr($target, 0, 1) != '#') { + $target = '#' . $target; + } + // This shouldn't be passed on to jQuery.ui.dialog. + unset($options['target']); } else { - // Generate the target wrapper for the dialog. - if (isset($options['target'])) { - // If the target was nominated in the incoming options, use that. - $target = $options['target']; - // Ensure the target includes the #. - if (substr($target, 0, 1) != '#') { - $target = '#' . $target; - } - // This shouldn't be passed on to jQuery.ui.dialog. - unset($options['target']); - } - else { - // Generate a target based on the route id. - $route_name = $route_match->getRouteName(); - $target = '#' . drupal_html_id("drupal-dialog-$route_name"); - } + // Generate a target based on the route id. + $route_name = $route_match->getRouteName(); + $target = '#' . drupal_html_id("drupal-dialog-$route_name"); } - $response->addCommand(new OpenDialogCommand($target, $title, $content, $options)); - return $response; + + return [ + $main_content, + isset($main_content['#title']) ? $main_content['#title'] : $this->titleResolver->getTitle($request, $route_match->getRouteObject()), + [ + 'dialog_options' => $options, + 'target' => $target, + ] + ]; } /** - * Returns the result of invoking the sub-controller. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. - * @param mixed $controller_definition - * A controller definition string, or a callable object/closure. - * - * @return mixed - * The result of invoking the controller. Render arrays, strings, HtmlPage, - * and HtmlFragment objects are possible. + * {@inheritdoc} */ - public function getContentResult(Request $request, $controller_definition) { - if ($controller_definition instanceof \Closure) { - $callable = $controller_definition; - } - else { - $callable = $this->controllerResolver->getControllerFromDefinition($controller_definition); - } - $arguments = $this->controllerResolver->getArguments($request, $callable); - $page_content = call_user_func_array($callable, $arguments); + public function renderContentIntoResponse(array $main_content, $title, array $custom) { + $content = drupal_render_root($main_content); + drupal_process_attached($main_content); - return $page_content; + $response = new AjaxResponse(); + $response->addCommand(new OpenDialogCommand($custom['target'], $title, $content, $custom['dialog_options'])); + return $response; } } diff --git a/core/lib/Drupal/Core/Controller/HtmlController.php b/core/lib/Drupal/Core/Controller/HtmlController.php new file mode 100644 index 0000000..79bce4a --- /dev/null +++ b/core/lib/Drupal/Core/Controller/HtmlController.php @@ -0,0 +1,277 @@ +titleResolver = $title_resolver; + $this->displayVariantManager = $display_variant_manager; + $this->eventDispatcher = $event_dispatcher; + } + + /** + * {@inheritdoc} + * + * The HTML body: wraps the main content in #type 'page'. + */ + public function prepareContent(array $main_content, Request $request, RouteMatchInterface $route_match) { + // If the _content result already is #type => page, we have no work to do: + // the "main content" already is an entire "page" (see html.html.twig). + if (isset($main_content['#type']) && $main_content['#type'] === 'page') { + $page = $main_content; + } + // Otherwise, render it as the main content of a #type => page, by selecting + // page display variant to do that and building that page display variant. + else { + // Select the page display variant to be used to render this main content, + // default to the built-in "simple page". + $event = new PageDisplayVariantSelectionEvent('simple_page'); + $this->eventDispatcher->dispatch(SystemEvents::SELECT_PAGE_DISPLAY_VARIANT, $event); + $variant_id = $event->getPluginId(); + + // We must render the main content now already, because it might provide a + // title. We set its $is_root_call parameter to FALSE, to ensure + // #post_render_cache callbacks are not yet applied. This is essentially + // "pre-rendering" the main content, the "full rendering" will happen in + // ::renderContentIntoResponse(). + // @todo Remove this once https://www.drupal.org/node/2359901 lands. + if (!empty($main_content)) { + drupal_render($main_content, FALSE); + $main_content = [ + '#markup' => SafeMarkup::set($main_content['#markup']), + '#attached' => $main_content['#attached'], + '#cache' => ['tags' => $main_content['#cache']['tags']], + '#post_render_cache' => $main_content['#post_render_cache'], + '#title' => isset($main_content['#title']) ? $main_content['#title'] : NULL, + ]; + } + + // Instantiate the page display, and give it the main content. + $page_display = $this->displayVariantManager->createInstance($variant_id); + if (!$page_display instanceof PageVariantInterface) { + throw new \LogicException('Cannot render the main content for this page because the provided display variant does not implement PageVariantInterface.'); + } + $page_display->setMainContent($main_content); + + // Generate a #type => page render array using the page display variant, + // the page display will build the content for the various page regions. + $page = array( + '#type' => 'page', + ); + $page += $page_display->build(); + } + + // $page is now fully built. Find all non-empty page regions, and add a + // theme wrapper function that allows them to be consistently themed. + $regions = system_region_list(\Drupal::theme()->getActiveTheme()->getName()); + foreach (array_keys($regions) as $region) { + if (!empty($page[$region])) { + $page[$region]['#theme_wrappers'][] = 'region'; + $page[$region]['#region'] = $region; + } + } + + // Allow hooks to add attachments to $page['#attached']. + static::invokePageAttachmentHooks($page); + + // Determine the title: use the provided one if available, otherwise get it + // from the routing information. + $title = NULL; + if (isset($main_content['#title'])) { + $title = $main_content['#title']; + } + else { + $title = $this->titleResolver->getTitle($request, $route_match->getRouteObject()); + } + + return [$page, $title, []]; + } + + /** + * {@inheritdoc} + * + * The entire HTML: takes a #type 'page' and wraps it in a #type 'html'. + */ + public function renderContentIntoResponse(array $page, $title, array $custom) { + if (!isset($page['#type']) || $page['#type'] !== 'page') { + throw new \LogicException('Must be #type page'); + } + + $page['#title'] = $title; + + // Now render the rendered page.html.twig template inside the html.html.twig + // template, and use the bubbled #attached metadata from $page to ensure we + // load all attached assets. + $html = [ + '#type' => 'html', + 'page' => $page, + ]; + $html += element_info('html'); + + // The special page regions will appear directly in html.html.twig, not in + // page.html.twig, hence add them here, just before rendering html.html.twig. + static::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(). + drupal_render_root($html['page']); + if (isset($html['page_top'])) { + drupal_render_root($html['page_top']); + } + if (isset($html['page_bottom'])) { + drupal_render_root($html['page_bottom']); + } + $content = drupal_render_root($html); + + // Store the cache tags associated with this page in a X-Drupal-Cache-Tags + // header. Also associate the "rendered" cache tag. This allows us to + // invalidate the entire render cache, regardless of the cache bin. + $cache_tags = Cache::mergeTags( + isset($html['page_top']) ? $html['page_top']['#cache']['tags'] : [], + $html['page']['#cache']['tags'], + isset($html['page_bottom']) ? $html['page_bottom']['#cache']['tags'] : [], + ['rendered'] + ); + + // Set the generator in the HTTP header. + list($version) = explode('.', \Drupal::VERSION, 2); + + return new Response($content, 200,[ + 'X-Drupal-Cache-Tags' => implode(' ', $cache_tags), + 'X-Generator' => 'Drupal ' . $version . ' (http://drupal.org)' + ]); + } + + /** + * Invokes the page attachment hooks. + * + * @param array &$page + * A #type 'page' render array, for which the page attachment hooks will be + * invoked and to which the results will be added. + * + * @throws \LogicException + * + * @see hook_page_attachments() + * @see hook_page_attachments_alter() + */ + public static function invokePageAttachmentHooks(array &$page) { + // Modules can add attachments. + $attachments = []; + foreach (\Drupal::moduleHandler()->getImplementations('page_attachments') as $module) { + $function = $module . '_page_attachments'; + $function($attachments); + } + if (array_diff(array_keys($attachments), ['#attached', '#post_render_cache']) !== []) { + throw new \LogicException('Only #attached and #post_render_cache may be set in hook_page_attachments().'); + } + + // Modules and themes can alter page attachments. + \Drupal::moduleHandler()->alter('page_attachments', $attachments); + \Drupal::theme()->alter('page_attachments', $attachments); + if (array_diff(array_keys($attachments), ['#attached', '#post_render_cache']) !== []) { + throw new \LogicException('Only #attached and #post_render_cache may be set in hook_page_attachments_alter().'); + } + if (isset($attachments['#attached'])) { + $page['#attached'] = $attachments['#attached']; + } + if (isset($attachments['#post_render_cache'])) { + $page['#post_render_cache'] = $attachments['#post_render_cache']; + } + } + + /** + * Invokes the page top and bottom hooks. + * + * @param array &$html + * A #type 'html' render array, for which the page top and bottom hooks will + * be invoked, and to which the 'page_top' and 'page_bottom' children (also + * render arrays) will be added (if non-empty). + * + * @throws \LogicException + * + * @see hook_page_top() + * @see hook_page_bottom() + * @see html.html.twig + */ + public static function buildPageTopAndBottom(array &$html) { + // Modules can add render arrays to the top and bottom of the page. + $page_top = []; + $page_bottom = []; + foreach (\Drupal::moduleHandler()->getImplementations('page_top') as $module) { + $function = $module . '_page_top'; + $function($page_top); + } + foreach (\Drupal::moduleHandler()->getImplementations('page_bottom') as $module) { + $function = $module . '_page_bottom'; + $function($page_bottom); + } + if (!empty($page_top)) { + $html['page_top'] = $page_top; + } + if (!empty($page_bottom)) { + $html['page_bottom'] = $page_bottom; + } + } + +} diff --git a/core/lib/Drupal/Core/Controller/HtmlControllerBase.php b/core/lib/Drupal/Core/Controller/HtmlControllerBase.php deleted file mode 100644 index b2a639b..0000000 diff --git a/core/lib/Drupal/Core/Controller/HtmlPageController.php b/core/lib/Drupal/Core/Controller/HtmlPageController.php deleted file mode 100644 index efc11ac..0000000 diff --git a/core/lib/Drupal/Core/Controller/MainContentControllerBase.php b/core/lib/Drupal/Core/Controller/MainContentControllerBase.php new file mode 100644 index 0000000..64517fc --- /dev/null +++ b/core/lib/Drupal/Core/Controller/MainContentControllerBase.php @@ -0,0 +1,87 @@ +controllerResolver = $controller_resolver; + } + + /** + * {@inheritdoc} + */ + public function getMainContent(Request $request, $controller_definition) { + if ($controller_definition instanceof \Closure) { + $callable = $controller_definition; + } + else { + $callable = $this->controllerResolver->getControllerFromDefinition($controller_definition); + } + $arguments = $this->controllerResolver->getArguments($request, $callable); + $main_content = call_user_func_array($callable, $arguments); + + return $main_content; + } + + /** + * {@inheritdoc} + */ + public function prepareContent(array $main_content, Request $request, RouteMatchInterface $route_match) { + // In this default implementation: + return [ + // We return $main_content verbatim. + $main_content, + // We don't provide a title. + NULL, + // No custom options. + [], + ]; + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, RouteMatchInterface $route_match, $_content) { + $main_content = $this->getMainContent($request, $_content); + + // If the received content already is a response, just pass it through. This + // may happen e.g. when a _content callable chooses to perform a redirect. + if ($main_content instanceof Response) { + return $main_content; + } + + if (!is_array($main_content)) { + throw new \LogicException('Invalid render array returned by ' . $_content . '.'); + } + + list($content, $title, $custom) = $this->prepareContent($main_content, $request, $route_match); + return $this->renderContentIntoResponse($content, $title, $custom); + } + +} diff --git a/core/lib/Drupal/Core/Controller/MainContentControllerInterface.php b/core/lib/Drupal/Core/Controller/MainContentControllerInterface.php new file mode 100644 index 0000000..02859f5 --- /dev/null +++ b/core/lib/Drupal/Core/Controller/MainContentControllerInterface.php @@ -0,0 +1,92 @@ +addCommand(new OpenModalDialogCommand($title, $content, $custom['dialog_options'])); + return $response; + } + +} diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index db54f91..1f2c7b8 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -20,6 +20,7 @@ use Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass; use Drupal\Core\DependencyInjection\Compiler\RegisterServicesForDestructionPass; use Drupal\Core\Plugin\PluginManagerPass; +use Drupal\Core\Render\MainContentControllerPass; use Symfony\Component\DependencyInjection\Compiler\PassConfig; /** @@ -54,6 +55,8 @@ public function register(ContainerBuilder $container) { $container->addCompilerPass(new StackedKernelPass()); + $container->addCompilerPass(new MainContentControllerPass()); + // Collect tagged handler services as method calls on consumer services. $container->addCompilerPass(new TaggedHandlersPass()); $container->addCompilerPass(new RegisterStreamWrappersPass()); diff --git a/core/lib/Drupal/Core/Display/Annotation/DisplayVariant.php b/core/lib/Drupal/Core/Display/Annotation/DisplayVariant.php index dd5a132..df1b52a 100644 --- a/core/lib/Drupal/Core/Display/Annotation/DisplayVariant.php +++ b/core/lib/Drupal/Core/Display/Annotation/DisplayVariant.php @@ -13,9 +13,7 @@ * Defines a display variant annotation object. * * Display variants are used to dictate the output of a given Display, which - * can be used to control the output of many parts of Drupal. For example, the - * FullPageVariant is used by the Block module to control regions and output - * block content placed in those regions. + * can be used to control the output of many parts of Drupal. * * Variants are usually chosen by some selection criteria, and are instantiated * directly. Each variant must define its own approach to rendering, and can @@ -27,12 +25,15 @@ * * Plugin namespace: Plugin\DisplayVariant * - * For a working example, see - * \Drupal\block\Plugin\DisplayVariant\FullPageVariant + * For working examples, see + * - \Drupal\system\Plugin\DisplayVariant\SimplePageVariant + * - \Drupal\block\Plugin\DisplayVariant\BlockPageVariant + * - \Drupal\block\Plugin\DisplayVariant\DemoBlockPageVariant * * @see \Drupal\Core\Display\VariantInterface * @see \Drupal\Core\Display\VariantBase * @see \Drupal\Core\Display\VariantManager + * @see \Drupal\Core\Display\PageVariantInterface * @see plugin_api * * @Annotation diff --git a/core/lib/Drupal/Core/Display/Annotation/PageDisplayVariant.php b/core/lib/Drupal/Core/Display/Annotation/PageDisplayVariant.php new file mode 100644 index 0000000..bc07297 --- /dev/null +++ b/core/lib/Drupal/Core/Display/Annotation/PageDisplayVariant.php @@ -0,0 +1,26 @@ +negotiation = $negotiation; + $this->mainContentControllers = $main_content_controllers; } /** - * Associative array of supported mime types and their appropriate controller. - * - * @var array - */ - protected $types = array( - 'drupal_dialog' => 'controller.dialog:dialog', - 'drupal_modal' => 'controller.dialog:modal', - 'html' => 'controller.page:content', - 'drupal_ajax' => 'controller.ajax:content', - ); - - /** * Sets the derived request format on the request. * * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event @@ -61,18 +80,21 @@ public function onRequestDeriveFormat(GetResponseEvent $event) { } /** - * Sets the _controller on a request based on the request format. + * Sets the derived _controller on the request, based on the request format. * * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event * The event to process. */ - public function onRequestDeriveContentWrapper(GetResponseEvent $event) { + public function onRequestDeriveController(GetResponseEvent $event) { $request = $event->getRequest(); $controller = $request->attributes->get('_controller'); - if (empty($controller) && ($type = $request->getRequestFormat())) { - if (isset($this->types[$type])) { - $request->attributes->set('_controller', $this->types[$type]); + if (empty($controller) && ($format = $request->getRequestFormat())) { + if (isset($this->mainContentControllers[$format])) { + $controller = $this->mainContentControllers[$format]; + // MainContentControllerInterface dictates the method for handling a + // request is named ::handle(). Use 'service:method' notation. + $request->attributes->set('_controller', $controller . ':handle'); } } } @@ -85,7 +107,7 @@ public function onRequestDeriveContentWrapper(GetResponseEvent $event) { */ static function getSubscribedEvents() { $events[KernelEvents::REQUEST][] = array('onRequestDeriveFormat', 31); - $events[KernelEvents::REQUEST][] = array('onRequestDeriveContentWrapper', 30); + $events[KernelEvents::REQUEST][] = array('onRequestDeriveController', 30); return $events; } diff --git a/core/lib/Drupal/Core/EventSubscriber/CustomPageExceptionHtmlSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/CustomPageExceptionHtmlSubscriber.php index ae3d944..eb054a0 100644 --- a/core/lib/Drupal/Core/EventSubscriber/CustomPageExceptionHtmlSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/CustomPageExceptionHtmlSubscriber.php @@ -19,7 +19,7 @@ /** * Exception subscriber for handling core custom error pages. */ -class CustomPageExceptionHtmlSubscriber extends HttpExceptionSubscriberBase { +class CustomPageExceptionHtmlSubscriber extends DefaultExceptionHtmlSubscriber { /** * The configuration factory. @@ -36,13 +36,6 @@ class CustomPageExceptionHtmlSubscriber extends HttpExceptionSubscriberBase { protected $aliasManager; /** - * The HTTP kernel. - * - * @var \Symfony\Component\HttpKernel\HttpKernelInterface - */ - protected $httpKernel; - - /** * The logger instance. * * @var \Psr\Log\LoggerInterface @@ -62,9 +55,9 @@ class CustomPageExceptionHtmlSubscriber extends HttpExceptionSubscriberBase { * The logger service. */ public function __construct(ConfigFactoryInterface $config_factory, AliasManagerInterface $alias_manager, HttpKernelInterface $http_kernel, LoggerInterface $logger) { + parent::__construct($http_kernel); $this->configFactory = $config_factory; $this->aliasManager = $alias_manager; - $this->httpKernel = $http_kernel; $this->logger = $logger; } @@ -76,17 +69,7 @@ protected static function getPriority() { } /** - * {@inheritDoc} - */ - protected function getHandledFormats() { - return ['html']; - } - - /** - * Handles a 403 error for HTML. - * - * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event - * The event to process. + * {@inheritdoc} */ public function on403(GetResponseForExceptionEvent $event) { $path = $this->aliasManager->getPathByAlias($this->configFactory->get('system.site')->get('page.403')); @@ -94,10 +77,7 @@ public function on403(GetResponseForExceptionEvent $event) { } /** - * Handles a 404 error for HTML. - * - * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event - * The event to process. + * {@inheritdoc} */ public function on404(GetResponseForExceptionEvent $event) { $path = $this->aliasManager->getPathByAlias($this->configFactory->get('system.site')->get('page.404')); @@ -105,44 +85,18 @@ public function on404(GetResponseForExceptionEvent $event) { } /** - * Makes a subrequest to retrieve a custom error page. - * - * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event - * The event to process - * @param string $path - * The path to which to make a subrequest for this error message. - * @param int $status_code - * The status code for the error being handled. + * {@inheritdoc} */ protected function makeSubrequest(GetResponseForExceptionEvent $event, $path, $status_code) { - $request = $event->getRequest(); - - // @todo Remove dependency on the internal _system_path attribute: - // https://www.drupal.org/node/2293523. - $system_path = $request->attributes->get('_system_path'); - - if ($path && $path != $system_path) { - // @todo The create() method expects a slash-prefixed path, but we store a - // normal system path in the site_404 variable. - if ($request->getMethod() === 'POST') { - $sub_request = Request::create($request->getBaseUrl() . '/' . $path, 'POST', ['destination' => $system_path, '_exception_statuscode' => $status_code] + $request->request->all(), $request->cookies->all(), [], $request->server->all()); - } - else { - $sub_request = Request::create($request->getBaseUrl() . '/' . $path, 'GET', $request->query->all() + ['destination' => $system_path, '_exception_statuscode' => $status_code], $request->cookies->all(), [], $request->server->all()); - } - - try { - $response = $this->httpKernel->handle($sub_request, HttpKernelInterface::SUB_REQUEST); - $response->setStatusCode($status_code); - $event->setResponse($response); - } - catch (\Exception $e) { - // If an error happened in the subrequest we can't do much else. - // Instead, just log it. The DefaultExceptionHandler will catch the - // original exception and handle it normally. - $error = Error::decodeException($e); - $this->logger->log($error['severity_level'], '%type: !message in %function (line %line of %file).', $error); - } + try { + parent::makeSubrequest($event, $path, $status_code); + } + catch (\Exception $e) { + // If an error happened in the subrequest we can't do much else. Instead, + // just log it. The DefaultExceptionSubscriber will catch the original + // exception and handle it normally. + $error = Error::decodeException($e); + $this->logger->log($error['severity_level'], '%type: !message in %function (line %line of %file).', $error); } } diff --git a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php index 0df6a30..cef8eb4 100644 --- a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php @@ -7,44 +7,31 @@ namespace Drupal\Core\EventSubscriber; -use Drupal\Core\Page\HtmlFragment; -use Drupal\Core\Page\HtmlFragmentRendererInterface; -use Drupal\Core\Page\HtmlPageRendererInterface; -use Drupal\Core\StringTranslation\StringTranslationTrait; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; /** - * Handle most HTTP errors for HTML. + * Exception subscriber for handling core default error pages. */ class DefaultExceptionHtmlSubscriber extends HttpExceptionSubscriberBase { - use StringTranslationTrait; /** - * The HTML fragment renderer. + * The HTTP kernel. * - * @var \Drupal\Core\Page\HtmlFragmentRendererInterface + * @var \Symfony\Component\HttpKernel\HttpKernelInterface */ - protected $fragmentRenderer; - - /** - * The HTML page renderer. - * - * @var \Drupal\Core\Page\HtmlPageRendererInterface - */ - protected $htmlPageRenderer; + protected $httpKernel; /** * Constructs a new DefaultExceptionHtmlSubscriber. * - * @param \Drupal\Core\Page\HtmlFragmentRendererInterface $fragment_renderer - * The fragment renderer. - * @param \Drupal\Core\Page\HtmlPageRendererInterface $page_renderer - * The page renderer. + * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel + * The HTTP kernel. */ - public function __construct(HtmlFragmentRendererInterface $fragment_renderer, HtmlPageRendererInterface $page_renderer) { - $this->fragmentRenderer = $fragment_renderer; - $this->htmlPageRenderer = $page_renderer; + public function __construct(HttpKernelInterface $http_kernel) { + $this->httpKernel = $http_kernel; } /** @@ -70,9 +57,7 @@ protected function getHandledFormats() { * The event to process. */ public function on403(GetResponseForExceptionEvent $event) { - $response = $this->createResponse($this->t('Access denied'), $this->t('You are not authorized to access this page.'), Response::HTTP_FORBIDDEN); - $response->headers->set('Content-type', 'text/html'); - $event->setResponse($response); + $this->makeSubrequest($event, 'system/403', Response::HTTP_FORBIDDEN); } /** @@ -82,40 +67,41 @@ public function on403(GetResponseForExceptionEvent $event) { * The event to process. */ public function on404(GetResponseForExceptionEvent $event) { - $path = $event->getRequest()->getPathInfo(); - $response = $this->createResponse($this->t('Page not found'), $this->t('The requested page "@path" could not be found.', ['@path' => $path]), Response::HTTP_NOT_FOUND); - $response->headers->set('Content-type', 'text/html'); - $event->setResponse($response); + $this->makeSubrequest($event, 'system/404', Response::HTTP_NOT_FOUND); } /** - * Handles a 405 error for HTML. + * Makes a subrequest to retrieve the default error page. * * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event - * The event to process. + * The event to process + * @param string $path + * The path to which to make a subrequest for this error message. + * @param int $status_code + * The status code for the error being handled. */ - public function on405(GetResponseForExceptionEvent $event) { - $response = new Response('Method Not Allowed', Response::HTTP_METHOD_NOT_ALLOWED); - $response->headers->set('Content-type', 'text/html'); - $event->setResponse($response); - } + protected function makeSubrequest(GetResponseForExceptionEvent $event, $path, $status_code) { + $request = $event->getRequest(); - /** - * @param $title - * The page title of the response. - * @param $body - * The body of the error page. - * @param $response_code - * The HTTP response code of the response. - * @return Response - * An error Response object ready to return to the browser. - */ - protected function createResponse($title, $body, $response_code) { - $fragment = new HtmlFragment($body); - $fragment->setTitle($title); + // @todo Remove dependency on the internal _system_path attribute: + // https://www.drupal.org/node/2293523. + $system_path = $request->attributes->get('_system_path'); + + if ($path && $path != $system_path) { + if ($request->getMethod() === 'POST') { + $sub_request = Request::create($request->getBaseUrl() . '/' . $path, 'POST', ['destination' => $system_path, '_exception_statuscode' => $status_code] + $request->request->all(), $request->cookies->all(), [], $request->server->all()); + } + else { + $sub_request = Request::create($request->getBaseUrl() . '/' . $path, 'GET', $request->query->all() + ['destination' => $system_path, '_exception_statuscode' => $status_code], $request->cookies->all(), [], $request->server->all()); + } + + // Persist the 'exception' attribute to the subrequest. + $sub_request->attributes->set('exception', $request->attributes->get('exception')); - $page = $this->fragmentRenderer->render($fragment, $response_code); - return new Response($this->htmlPageRenderer->render($page), $page->getStatusCode()); + $response = $this->httpKernel->handle($sub_request, HttpKernelInterface::SUB_REQUEST); + $response->setStatusCode($status_code); + $event->setResponse($response); + } } } diff --git a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php index e35eff4..b8d23c4 100644 --- a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php @@ -11,10 +11,7 @@ use Drupal\Component\Utility\String; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\ContentNegotiation; -use Drupal\Core\Page\DefaultHtmlPageRenderer; -use Drupal\Core\Page\HtmlFragment; -use Drupal\Core\Page\HtmlFragmentRendererInterface; -use Drupal\Core\Page\HtmlPageRendererInterface; +use Drupal\Core\Render\BareHtmlPageRendererInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Utility\Error; use Symfony\Component\Debug\Exception\FlattenException; @@ -36,20 +33,6 @@ class DefaultExceptionSubscriber implements EventSubscriberInterface { use StringTranslationTrait; /** - * The fragment renderer. - * - * @var \Drupal\Core\Page\HtmlFragmentRendererInterface - */ - protected $fragmentRenderer; - - /** - * The page renderer. - * - * @var \Drupal\Core\Page\HtmlPageRendererInterface - */ - protected $htmlPageRenderer; - - /** * @var string * * One of the error level constants defined in bootstrap.inc. @@ -64,19 +47,23 @@ class DefaultExceptionSubscriber implements EventSubscriberInterface { protected $configFactory; /** + * The bare HTML page renderer. + * + * @var \Drupal\Core\Render\BareHtmlPageRendererInterface + */ + protected $bareHtmlPageRenderer; + + /** * Constructs a new DefaultExceptionHtmlSubscriber. * - * @param \Drupal\Core\Page\HtmlFragmentRendererInterface $fragment_renderer - * The fragment renderer. - * @param \Drupal\Core\Page\HtmlPageRendererInterface $page_renderer - * The page renderer. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The configuration factory. + * @param \Drupal\Core\Render\BareHtmlPageRendererInterface $bare_html_page_renderer + * The bare HTML page renderer. */ - public function __construct(HtmlFragmentRendererInterface $fragment_renderer, HtmlPageRendererInterface $page_renderer, ConfigFactoryInterface $config_factory) { - $this->fragmentRenderer = $fragment_renderer; - $this->htmlPageRenderer = $page_renderer; + public function __construct(ConfigFactoryInterface $config_factory, BareHtmlPageRendererInterface $bare_html_page_renderer) { $this->configFactory = $config_factory; + $this->bareHtmlPageRenderer = $bare_html_page_renderer; } /** @@ -145,7 +132,7 @@ protected function onHtml(GetResponseForExceptionEvent $event) { } $content = $this->t('The website has encountered an error. Please try again later.'); - $output = DefaultHtmlPageRenderer::renderPage($content, $this->t('Error')); + $output = $this->bareHtmlPageRenderer->renderMaintenancePage($content, $this->t('Error')); $response = new Response($output); if ($exception instanceof HttpExceptionInterface) { @@ -186,26 +173,6 @@ protected function onJson(GetResponseForExceptionEvent $event) { } /** - * Creates an Html response for the provided criteria. - * - * @param $title - * The page title of the response. - * @param $body - * The body of the error page. - * @param $response_code - * The HTTP response code of the response. - * @return \Symfony\Component\HttpFoundation\Response - * An error Response object ready to return to the browser. - */ - protected function createHtmlResponse($title, $body, $response_code) { - $fragment = new HtmlFragment($body); - $fragment->setTitle($title); - - $page = $this->fragmentRenderer->render($fragment, $response_code); - return new Response($this->htmlPageRenderer->render($page), $page->getStatusCode()); - } - - /** * Handles errors for this subscriber. * * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event diff --git a/core/lib/Drupal/Core/EventSubscriber/HtmlViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/HtmlViewSubscriber.php deleted file mode 100644 index b51e491..0000000 diff --git a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php index f09e3e3..164dec0 100644 --- a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php @@ -10,7 +10,7 @@ use Drupal\Component\Utility\String; use Drupal\Component\Utility\Xss; use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Page\DefaultHtmlPageRenderer; +use Drupal\Core\Render\BareHtmlPageRendererInterface; use Drupal\Core\Routing\RouteMatch; use Drupal\Core\Routing\UrlGeneratorInterface; use Drupal\Core\Session\AccountInterface; @@ -58,6 +58,13 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { protected $urlGenerator; /** + * The bare HTML page renderer. + * + * @var \Drupal\Core\Render\BareHtmlPageRendererInterface + */ + protected $bareHtmlPageRenderer; + + /** * Constructs a new MaintenanceModeSubscriber. * * @param \Drupal\Core\Site\MaintenanceModeInterface $maintenance_mode @@ -70,13 +77,16 @@ class MaintenanceModeSubscriber implements EventSubscriberInterface { * The url generator. * @param \Drupal\Core\Session\AccountInterface $account * The current user. + * @param \Drupal\Core\Render\BareHtmlPageRendererInterface $bare_html_page_renderer + * The bare HTML page renderer. */ - public function __construct(MaintenanceModeInterface $maintenance_mode, ConfigFactoryInterface $config_factory, TranslationInterface $translation, UrlGeneratorInterface $url_generator, AccountInterface $account) { + public function __construct(MaintenanceModeInterface $maintenance_mode, ConfigFactoryInterface $config_factory, TranslationInterface $translation, UrlGeneratorInterface $url_generator, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer) { $this->maintenanceMode = $maintenance_mode; $this->config = $config_factory; $this->stringTranslation = $translation; $this->urlGenerator = $url_generator; $this->account = $account; + $this->bareHtmlPageRenderer = $bare_html_page_renderer; } /** @@ -95,11 +105,8 @@ public function onKernelRequestMaintenance(GetResponseEvent $event) { $content = Xss::filterAdmin(String::format($this->config->get('system.maintenance')->get('message'), array( '@site' => $this->config->get('system.site')->get('name'), ))); - // @todo Break the dependency on DefaultHtmlPageRenderer, see: - // https://www.drupal.org/node/2295609 - $content = DefaultHtmlPageRenderer::renderPage($content, $this->t('Site under maintenance')); - $response = new Response('Service unavailable', 503); - $response->setContent($content); + $output = $this->bareHtmlPageRenderer->renderMaintenancePage($content, $this->t('Site under maintenance')); + $response = new Response($output, 503); $event->setResponse($response); } else { diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php index 7f13698..0b0c274 100644 --- a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php @@ -7,9 +7,7 @@ namespace Drupal\Core\EventSubscriber; -use Drupal\Core\Ajax\AjaxResponseRenderer; use Drupal\Core\Controller\TitleResolverInterface; -use Drupal\Core\Page\HtmlPage; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\JsonResponse; @@ -43,12 +41,6 @@ class ViewSubscriber implements EventSubscriberInterface { */ protected $titleResolver; - /** - * The Ajax response renderer. - * - * @var \Drupal\Core\Ajax\AjaxResponseRenderer - */ - protected $ajaxRenderer; /** * Constructs a new ViewSubscriber. @@ -57,13 +49,10 @@ class ViewSubscriber implements EventSubscriberInterface { * The content negotiation. * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver * The title resolver. - * @param \Drupal\Core\Ajax\AjaxResponseRenderer $ajax_renderer - * The ajax response renderer. */ - public function __construct(ContentNegotiation $negotiation, TitleResolverInterface $title_resolver, AjaxResponseRenderer $ajax_renderer) { + public function __construct(ContentNegotiation $negotiation, TitleResolverInterface $title_resolver) { $this->negotiation = $negotiation; $this->titleResolver = $title_resolver; - $this->ajaxRenderer = $ajax_renderer; } /** @@ -79,14 +68,8 @@ public function __construct(ContentNegotiation $negotiation, TitleResolverInterf * The Event to process. */ public function onView(GetResponseForControllerResultEvent $event) { - $request = $event->getRequest(); - // For a master request, we process the result and wrap it as needed. - // For a subrequest, all we want is the string value. We assume that - // is just an HTML string from a controller, so wrap that into a response - // object. The subrequest's response will get dissected and placed into - // the larger page as needed. if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) { $method = 'on' . $this->negotiation->getContentType($request); @@ -97,27 +80,6 @@ public function onView(GetResponseForControllerResultEvent $event) { $event->setResponse(new Response('Not Acceptable', 406)); } } - else { - // This is a new-style Symfony-esque subrequest, which means we assume - // the body is not supposed to be a complete page but just a page - // fragment. - $page_result = $event->getControllerResult(); - if ($page_result instanceof HtmlPage || $page_result instanceof Response) { - return $page_result; - } - if (!is_array($page_result)) { - $page_result = array( - '#markup' => $page_result, - ); - } - - // If no title was returned fall back to one defined in the route. - if (!isset($page_result['#title'])) { - $page_result['#title'] = $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)); - } - - $event->setResponse(new Response(drupal_render_root($page_result))); - } } public function onJson(GetResponseForControllerResultEvent $event) { diff --git a/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php b/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php deleted file mode 100644 index 0cdd613..0000000 diff --git a/core/lib/Drupal/Core/Page/DefaultHtmlPageRenderer.php b/core/lib/Drupal/Core/Page/DefaultHtmlPageRenderer.php deleted file mode 100644 index eea3e16..0000000 diff --git a/core/lib/Drupal/Core/Page/FeedLinkElement.php b/core/lib/Drupal/Core/Page/FeedLinkElement.php deleted file mode 100644 index 648963b..0000000 diff --git a/core/lib/Drupal/Core/Page/HeadElement.php b/core/lib/Drupal/Core/Page/HeadElement.php deleted file mode 100644 index 49c421d..0000000 diff --git a/core/lib/Drupal/Core/Page/HtmlFragment.php b/core/lib/Drupal/Core/Page/HtmlFragment.php deleted file mode 100644 index a4e0e29..0000000 diff --git a/core/lib/Drupal/Core/Page/HtmlFragmentInterface.php b/core/lib/Drupal/Core/Page/HtmlFragmentInterface.php deleted file mode 100644 index f9c481b..0000000 diff --git a/core/lib/Drupal/Core/Page/HtmlFragmentRendererInterface.php b/core/lib/Drupal/Core/Page/HtmlFragmentRendererInterface.php deleted file mode 100644 index 32e0076..0000000 diff --git a/core/lib/Drupal/Core/Page/HtmlPage.php b/core/lib/Drupal/Core/Page/HtmlPage.php deleted file mode 100644 index e6c75f2..0000000 diff --git a/core/lib/Drupal/Core/Page/HtmlPageRendererInterface.php b/core/lib/Drupal/Core/Page/HtmlPageRendererInterface.php deleted file mode 100644 index dea7f64..0000000 diff --git a/core/lib/Drupal/Core/Page/LinkElement.php b/core/lib/Drupal/Core/Page/LinkElement.php deleted file mode 100644 index 04cb2a0..0000000 diff --git a/core/lib/Drupal/Core/Page/MetaElement.php b/core/lib/Drupal/Core/Page/MetaElement.php deleted file mode 100644 index d849ed9..0000000 diff --git a/core/lib/Drupal/Core/Page/RenderHtmlRenderer.php b/core/lib/Drupal/Core/Page/RenderHtmlRenderer.php deleted file mode 100644 index bc88bc3..0000000 diff --git a/core/lib/Drupal/Core/Page/RenderHtmlRendererInterface.php b/core/lib/Drupal/Core/Page/RenderHtmlRendererInterface.php deleted file mode 100644 index 4807be9..0000000 diff --git a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php new file mode 100644 index 0000000..5ec957e --- /dev/null +++ b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php @@ -0,0 +1,78 @@ + $content]; + } + $attributes = [ + 'class' => [ + 'maintenance-page', + ], + ]; + return $this->renderBarePage($content, $title, $page_additions, $attributes, 'maintenance_page'); + } + + /** + * {@inheritdoc} + */ + public function renderInstallPage($content, $title, array $page_additions = []) { + $attributes = [ + 'class' => [ + 'install-page', + ], + ]; + return $this->renderBarePage($content, $title, $page_additions, $attributes, 'install_page'); + } + + /** + * Renders a bare page. + * + * @param string|array $content + * The main content to render in the 'content' region. + * @param string $title + * The title for this maintenance page. + * @param array $page_additions + * Additional regions to add to the page. May also be used to pass the + * #show_messages property for #type 'page'. + * @param array $attributes + * Attributes to set on #type 'html'. + * @param string $page_theme_property + * The #theme property to set on #type 'page'. + * + * @return string + * The rendered HTML page. + */ + protected function renderBarePage(array $content, $title, array $page_additions, array $attributes, $page_theme_property) { + $html = [ + '#type' => 'html', + '#attributes' => $attributes, + 'page' => [ + '#type' => 'page', + '#theme' => $page_theme_property, + '#title' => $title, + 'content' => $content, + ] + $page_additions, + ]; + + // We must first render the contents of the html.html.twig template, see + // \Drupal\Core\Controller\HtmlController::renderPage() for details. + drupal_render_root($html['page']); + return drupal_render($html); + } + +} diff --git a/core/lib/Drupal/Core/Render/BareHtmlPageRendererInterface.php b/core/lib/Drupal/Core/Render/BareHtmlPageRendererInterface.php new file mode 100644 index 0000000..5c9ddc2 --- /dev/null +++ b/core/lib/Drupal/Core/Render/BareHtmlPageRendererInterface.php @@ -0,0 +1,72 @@ +. + * Provides a render element for an entire HTML page: plus its children. * * @RenderElement("html") */ @@ -18,8 +20,12 @@ class Html extends RenderElement { * {@inheritdoc} */ public function getInfo() { + $class = get_class($this); return array( '#theme' => 'html', + '#pre_render' => array( + array($class, 'preRenderHtml'), + ), // HTML5 Shiv '#attached' => array( 'library' => array('core/html5shiv'), @@ -27,4 +33,103 @@ public function getInfo() { ); } + /** + * #pre_render callback for the html element type. + * + * @param array $element + * A structured array containing the html element type build properties. + * + * @return array + * The processed element. + */ + public static function preRenderHtml(array $element) { + // Attach libraries and CSS used by this theme. + $active_theme = \Drupal::theme()->getActiveTheme(); + foreach ($active_theme->getLibraries() as $library) { + $element['#attached']['library'][] = $library; + } + foreach ($active_theme->getStyleSheets() as $media => $stylesheets) { + foreach ($stylesheets as $stylesheet) { + $element['#attached']['css'][$stylesheet] = array( + 'group' => CSS_AGGREGATE_THEME, + 'every_page' => TRUE, + 'media' => $media + ); + } + } + + // Attach favicon. + if (static::themeGetSetting('features.favicon')) { + $favicon = static::themeGetSetting('favicon.url'); + $type = static::themeGetSetting('favicon.mimetype'); + $element['#attached']['html_head_link'][][] = array( + 'rel' => 'shortcut icon', + 'href' => UrlHelper::stripDangerousProtocols($favicon), + 'type' => $type, + ); + } + + // Get the major Drupal version. + list($version, ) = explode('.', \Drupal::VERSION); + + // Attach default meta tags. + $meta_default = array( + // Make sure the Content-Type comes first because the IE browser may be + // vulnerable to XSS via encoding attacks from any content that comes + // before this META tag, such as a TITLE tag. + 'system_meta_content_type' => array( + '#tag' => 'meta', + '#attributes' => array( + 'name' => 'charset', + 'charset' => 'utf-8', + ), + // Security: This always has to be output first. + '#weight' => -1000, + ), + // Show Drupal and the major version number in the META GENERATOR tag. + 'system_meta_generator' => array( + '#type' => 'html_tag', + '#tag' => 'meta', + '#attributes' => array( + 'name' => 'Generator', + 'content' => 'Drupal ' . $version . ' (http://drupal.org)', + ), + ), + // Attach default mobile meta tags for responsive design. + 'MobileOptimized' => array( + '#tag' => 'meta', + '#attributes' => array( + 'name' => 'MobileOptimized', + 'content' => 'width', + ), + ), + 'HandheldFriendly' => array( + '#tag' => 'meta', + '#attributes' => array( + 'name' => 'HandheldFriendly', + 'content' => 'true', + ), + ), + 'viewport' => array( + '#tag' => 'meta', + '#attributes' => array( + 'name' => 'viewport', + 'content' => 'width=device-width, initial-scale=1.0', + ), + ), + ); + foreach ($meta_default as $key => $value) { + $element['#attached']['html_head'][] = [$value, $key]; + } + + return $element; + } + + /** + * Wraps theme_get_setting(). + */ + protected static function themeGetSetting($setting_name) { + return theme_get_setting($setting_name); + } + } diff --git a/core/lib/Drupal/Core/Render/Element/Page.php b/core/lib/Drupal/Core/Render/Element/Page.php index cf133af..0d0393e 100644 --- a/core/lib/Drupal/Core/Render/Element/Page.php +++ b/core/lib/Drupal/Core/Render/Element/Page.php @@ -8,7 +8,11 @@ namespace Drupal\Core\Render\Element; /** - * Provides a render element for an entire HTML page. + * Provides a render element for the content of an HTML page. + * + * This represents the "main part" of the HTML page's body; see html.html.twig. + * + * @todo Rename this to 'body'? * * @RenderElement("page") */ diff --git a/core/lib/Drupal/Core/Render/MainContentControllerPass.php b/core/lib/Drupal/Core/Render/MainContentControllerPass.php new file mode 100644 index 0000000..bce55b9 --- /dev/null +++ b/core/lib/Drupal/Core/Render/MainContentControllerPass.php @@ -0,0 +1,33 @@ +findTaggedServiceIds('main_content_controller') as $id => $attributes) { + $format = $attributes[0]['format']; + $main_content_renderers[$format] = $id; + } + $container->setParameter('main_content_controllers', $main_content_renderers); + } + +} diff --git a/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php index 4545c19..de41c44 100644 --- a/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php +++ b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php @@ -125,7 +125,7 @@ public function authenticate(Request $request) { } // Always register an IP-based failed login event. $this->flood->register('basic_auth.failed_login_ip', $flood_config->get('ip_window')); - return NULL; + return []; } /** diff --git a/core/modules/block/block.module b/core/modules/block/block.module index 95e1bd6..a294dc8 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -62,43 +62,22 @@ function block_theme() { } /** - * Renders blocks into their regions. - * - * @todo Clean this up as part of https://www.drupal.org/node/2352155. + * Implements hook_page_top(). */ -function _block_page_build(&$page) { - $theme = \Drupal::theme()->getActiveTheme()->getName(); - - // Fetch a list of regions for the current theme. - $all_regions = system_region_list($theme); - if (\Drupal::routeMatch()->getRouteName() != 'block.admin_demo') { - // Create a full page display variant, which will load blocks into their - // regions. - $page += \Drupal::service('plugin.manager.display_variant') - ->createInstance('full_page') - ->build(); - } - else { - // Append region description if we are rendering the regions demo page. - $visible_regions = array_keys(system_region_list($theme, REGIONS_VISIBLE)); - foreach ($visible_regions as $region) { - $description = '
' . $all_regions[$region] . '
'; - $page[$region]['block_description'] = array( - '#markup' => $description, - '#weight' => 15, - ); - } - $page['page_top']['backlink'] = array( +function block_page_top(array &$page_top) { + if (\Drupal::routeMatch()->getRouteName() === 'block.admin_demo') { + $theme = \Drupal::theme()->getActiveTheme()->getName(); + $page_top['backlink'] = array( '#type' => 'link', '#title' => t('Exit block region demonstration'), '#options' => array('attributes' => array('class' => array('block-demo-backlink'))), '#weight' => -10, ); if (\Drupal::config('system.theme')->get('default') == $theme) { - $page['page_top']['backlink']['#url'] = Url::fromRoute('block.admin_display'); + $page_top['backlink']['#url'] = Url::fromRoute('block.admin_display'); } else { - $page['page_top']['backlink']['#url'] = Url::fromRoute('block.admin_display_theme', ['theme' => $theme]); + $page_top['backlink']['#url'] = Url::fromRoute('block.admin_display_theme', ['theme' => $theme]); } } } diff --git a/core/modules/block/block.services.yml b/core/modules/block/block.services.yml index dd38a43..9c7be32 100644 --- a/core/modules/block/block.services.yml +++ b/core/modules/block/block.services.yml @@ -3,6 +3,11 @@ services: class: Drupal\block\Theme\AdminDemoNegotiator tags: - { name: theme_negotiator, priority: 1000 } + block.page_display_variant_subscriber: + class: Drupal\block\EventSubscriber\BlockPageDisplayVariantSubscriber + arguments: ['@current_route_match'] + tags: + - { name: event_subscriber } block.current_user_context: class: Drupal\block\EventSubscriber\CurrentUserContext arguments: ['@current_user', '@entity.manager'] diff --git a/core/modules/block/src/EventSubscriber/BlockPageDisplayVariantSubscriber.php b/core/modules/block/src/EventSubscriber/BlockPageDisplayVariantSubscriber.php new file mode 100644 index 0000000..b6127fb --- /dev/null +++ b/core/modules/block/src/EventSubscriber/BlockPageDisplayVariantSubscriber.php @@ -0,0 +1,64 @@ +routeMatch = $route_match; + } + + /** + * Selects the block page display variant. + * + * @param \Drupal\system\Event\PageDisplayVariantSelectionEvent $event + * The event to process. + */ + public function onSelectPageDisplayVariant(PageDisplayVariantSelectionEvent $event) { + if ($this->routeMatch->getRouteName() != 'block.admin_demo') { + $event->setPluginId('block_page'); + } + else { + $event->setPluginId('block_demo_page'); + } + } + + /** + * {@inheritdoc} + */ + static function getSubscribedEvents() { + $events[SystemEvents::SELECT_PAGE_DISPLAY_VARIANT][] = array('onSelectPageDisplayVariant'); + return $events; + } + +} diff --git a/core/modules/block/src/Plugin/DisplayVariant/FullPageVariant.php b/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php similarity index 77% rename from core/modules/block/src/Plugin/DisplayVariant/FullPageVariant.php rename to core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php index 7243362..8266bca 100644 --- a/core/modules/block/src/Plugin/DisplayVariant/FullPageVariant.php +++ b/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php @@ -2,11 +2,13 @@ /** * @file - * Contains \Drupal\block\Plugin\DisplayVariant\FullPageVariant. + * Contains \Drupal\block\Plugin\DisplayVariant\BlockPageVariant. */ namespace Drupal\block\Plugin\DisplayVariant; +use Drupal\Core\Block\MainContentBlockPluginInterface; +use Drupal\Core\Display\PageVariantInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityViewBuilderInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; @@ -16,14 +18,14 @@ use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Provides a display variant that represents the full page. + * Provides a page display variant that decorates the main content with blocks. * - * @DisplayVariant( - * id = "full_page", - * admin_label = @Translation("Full page") + * @PageDisplayVariant( + * id = "block_page", + * admin_label = @Translation("Page with blocks") * ) */ -class FullPageVariant extends VariantBase implements ContainerFactoryPluginInterface { +class BlockPageVariant extends VariantBase implements PageVariantInterface, ContainerFactoryPluginInterface { /** * The block storage. @@ -61,7 +63,14 @@ class FullPageVariant extends VariantBase implements ContainerFactoryPluginInter protected $themeNegotiator; /** - * Constructs a new FullPageVariant. + * The render array representing the main page content. + * + * @var array + */ + protected $mainContent; + + /** + * Constructs a new BlockPageVariant. * * @param array $configuration * A configuration array containing information about the plugin instance. @@ -114,13 +123,27 @@ protected function getTheme() { /** * {@inheritdoc} */ + public function setMainContent(array $main_content) { + $this->mainContent = $main_content; + } + + /** + * {@inheritdoc} + */ public function build() { + // Track whether a block that shows the main content is displayed or not. + $main_content_block_displayed = FALSE; + $build = array(); // Load all region content assigned via blocks. foreach ($this->getRegionAssignments() as $region => $blocks) { /** @var $blocks \Drupal\block\BlockInterface[] */ foreach ($blocks as $key => $block) { if ($block->access('view')) { + if ($block->getPlugin() instanceof MainContentBlockPluginInterface) { + $block->getPlugin()->setMainContent($this->mainContent); + $main_content_block_displayed = TRUE; + } $build[$region][$key] = $this->blockViewBuilder->view($block); } } @@ -129,6 +152,14 @@ public function build() { $build[$region]['#sorted'] = TRUE; } } + + // If no block that shows the main content is displayed, still show the main + // content. Otherwise the end user will see all displayed blocks, but not + // the main content they came for. + if (!$main_content_block_displayed) { + $build['content']['system_main'] = $this->mainContent; + } + return $build; } diff --git a/core/modules/block/src/Plugin/DisplayVariant/DemoBlockPageVariant.php b/core/modules/block/src/Plugin/DisplayVariant/DemoBlockPageVariant.php new file mode 100644 index 0000000..b1a450d --- /dev/null +++ b/core/modules/block/src/Plugin/DisplayVariant/DemoBlockPageVariant.php @@ -0,0 +1,134 @@ +routeMatch = $route_match; + $this->themeNegotiator = $theme_negotiator; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('current_route_match'), + $container->get('theme.negotiator') + ); + } + + /** + * Gets the current theme for this route. + * + * @return string + * The current theme. + */ + protected function getTheme() { + return $this->themeNegotiator->determineActiveTheme($this->routeMatch); + } + + /** + * {@inheritdoc} + */ + public function setMainContent(array $main_content) { + $this->mainContent = $main_content; + } + + /** + * {@inheritdoc} + */ + public function build() { + // @see \Drupal\block\Controller::demo() + $build = ['content' => $this->mainContent]; + + // Show descriptions in each visible region, nothing else. + $visible_regions = $this->getVisibleRegionNames(); + foreach (array_keys($visible_regions) as $region) { + $description = '
' . $visible_regions[$region] . '
'; + $build[$region]['block_description'] = array( + '#markup' => $description, + '#weight' => 15, + ); + } + return $build; + } + + /** + * Returns the human-readable list of regions keyed by machine name. + * + * @return array + * An array of human-readable region names keyed by machine name. + */ + protected function getVisibleRegionNames() { + return system_region_list($this->getTheme(), REGIONS_VISIBLE); + } + +} diff --git a/core/modules/block/src/Tests/BlockTest.php b/core/modules/block/src/Tests/BlockTest.php index 1996c9b..fce7341 100644 --- a/core/modules/block/src/Tests/BlockTest.php +++ b/core/modules/block/src/Tests/BlockTest.php @@ -44,12 +44,9 @@ function testBlockVisibility() { $this->drupalGet(''); $this->assertText($title, 'Block was displayed on the front page.'); - $this->drupalGet('user'); + $this->drupalGet('user/' . $this->adminUser->id()); $this->assertNoText($title, 'Block was not displayed according to block visibility rules.'); - $this->drupalGet('USER/' . $this->adminUser->id()); - $this->assertNoText($title, 'Block was not displayed according to block visibility rules regardless of path case.'); - // Confirm that the block is not displayed to anonymous users. $this->drupalLogout(); $this->drupalGet(''); diff --git a/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/FullPageVariantTest.php b/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php similarity index 59% rename from core/modules/block/tests/src/Unit/Plugin/DisplayVariant/FullPageVariantTest.php rename to core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php index 1cbcfe5..557d996b 100644 --- a/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/FullPageVariantTest.php +++ b/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php @@ -2,7 +2,7 @@ /** * @file - * Contains \Drupal\Tests\block\Unit\Plugin\DisplayVariant\FullPageVariantTest. + * Contains \Drupal\Tests\block\Unit\Plugin\DisplayVariant\BlockPageVariantTest. */ namespace Drupal\Tests\block\Unit\Plugin\DisplayVariant; @@ -10,10 +10,10 @@ use Drupal\Tests\UnitTestCase; /** - * @coversDefaultClass \Drupal\block\Plugin\DisplayVariant\FullPageVariant + * @coversDefaultClass \Drupal\block\Plugin\DisplayVariant\BlockPageVariant * @group block */ -class FullPageVariantTest extends UnitTestCase { +class BlockPageVariantTest extends UnitTestCase { /** * The block storage. @@ -51,7 +51,7 @@ class FullPageVariantTest extends UnitTestCase { * @param array $definition * The plugin definition array. * - * @return \Drupal\block\Plugin\DisplayVariant\FullPageVariant|\PHPUnit_Framework_MockObject_MockObject + * @return \Drupal\block\Plugin\DisplayVariant\BlockPageVariant|\PHPUnit_Framework_MockObject_MockObject * A mocked display variant plugin. */ public function setUpDisplayVariant($configuration = array(), $definition = array()) { @@ -59,19 +59,84 @@ public function setUpDisplayVariant($configuration = array(), $definition = arra $this->blockViewBuilder = $this->getMock('Drupal\Core\Entity\EntityViewBuilderInterface'); $this->routeMatch = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); $this->themeNegotiator = $this->getMock('Drupal\Core\Theme\ThemeNegotiatorInterface'); - return $this->getMockBuilder('Drupal\block\Plugin\DisplayVariant\FullPageVariant') + return $this->getMockBuilder('Drupal\block\Plugin\DisplayVariant\BlockPageVariant') ->setConstructorArgs(array($configuration, 'test', $definition, $this->blockStorage, $this->blockViewBuilder, $this->routeMatch, $this->themeNegotiator)) ->setMethods(array('getRegionNames')) ->getMock(); } + public function providerBuild() { + $blocks_config = array( + 'block1' => array( + TRUE, 'top', 0, FALSE, + ), + // Test a block without access. + 'block2' => array( + FALSE, 'bottom', 0, FALSE, + ), + // Test two blocks in the same region with specific weight. + 'block3' => array( + TRUE, 'bottom', 5, FALSE, + ), + 'block4' => array( + TRUE, 'bottom', -5, FALSE, + ), + // Test a block implementing MainContentBlockPluginInterface. + 'block5' => array( + TRUE, 'center', 0, TRUE, + ), + ); + + $test_cases = []; + $test_cases[] = [$blocks_config, 4, + [ + 'top' => [ + 'block1' => [], + '#sorted' => TRUE, + ], + // The main content was rendered via a block. + 'center' => [ + 'block5' => [], + '#sorted' => TRUE, + ], + 'bottom' => [ + 'block4' => [], + 'block3' => [], + '#sorted' => TRUE, + ], + ], + ]; + unset($blocks_config['block5']); + $test_cases[] = [$blocks_config, 3, + [ + 'top' => [ + 'block1' => [], + '#sorted' => TRUE, + ], + 'bottom' => [ + 'block4' => [], + 'block3' => [], + '#sorted' => TRUE, + ], + // The main content was rendered via the fallback in case there is no + // block rendering the main content. + 'content' => [ + 'system_main' => ['#markup' => 'Hello kittens!'], + ], + ], + ]; + return $test_cases; + } + /** * Tests the building of a full page variant. * * @covers ::build * @covers ::getRegionAssignments + * + * @dataProvider providerBuild */ - public function testBuild() { + public function testBuild(array $blocks_config, $visible_block_count, array $expected_render_array) { $theme = $this->randomMachineName(); $display_variant = $this->setUpDisplayVariant(); $this->themeNegotiator->expects($this->any()) @@ -82,26 +147,14 @@ public function testBuild() { ->method('getRegionNames') ->will($this->returnValue(array( 'top' => 'Top', + 'center' => 'Center', 'bottom' => 'Bottom', ))); + $display_variant->setMainContent(['#markup' => 'Hello kittens!']); - $blocks_config = array( - 'block1' => array( - TRUE, 'top', 0, - ), - // Test a block without access. - 'block2' => array( - FALSE, 'bottom', 0, - ), - // Test two blocks in the same region with specific weight. - 'block3' => array( - TRUE, 'bottom', 5, - ), - 'block4' => array( - TRUE, 'bottom', -5, - ), - ); $blocks = array(); + $block_plugin = $this->getMock('Drupal\Core\Block\BlockPluginInterface'); + $main_content_block_plugin = $this->getMock('Drupal\Core\Block\MainContentBlockPluginInterface'); foreach ($blocks_config as $block_id => $block_config) { $block = $this->getMock('Drupal\block\BlockInterface'); $block->expects($this->once()) @@ -114,10 +167,13 @@ public function testBuild() { array('weight', $block_config[2]), array('status', TRUE), ))); + $block->expects($this->any()) + ->method('getPlugin') + ->willReturn($block_config[3] ? $main_content_block_plugin : $block_plugin); $blocks[$block_id] = $block; } - $this->blockViewBuilder->expects($this->exactly(3)) + $this->blockViewBuilder->expects($this->exactly($visible_block_count)) ->method('view') ->will($this->returnValue(array())); $this->blockStorage->expects($this->once()) @@ -125,18 +181,7 @@ public function testBuild() { ->with(array('theme' => $theme)) ->will($this->returnValue($blocks)); - $expected = array( - 'top' => array( - 'block1' => array(), - '#sorted' => TRUE, - ), - 'bottom' => array( - 'block4' => array(), - 'block3' => array(), - '#sorted' => TRUE, - ), - ); - $this->assertSame($expected, $display_variant->build()); + $this->assertSame($expected_render_array, $display_variant->build()); } } diff --git a/core/modules/node/src/Tests/Views/FrontPageTest.php b/core/modules/node/src/Tests/Views/FrontPageTest.php index 13c93e3..ebb7347 100644 --- a/core/modules/node/src/Tests/Views/FrontPageTest.php +++ b/core/modules/node/src/Tests/Views/FrontPageTest.php @@ -30,7 +30,7 @@ class FrontPageTest extends ViewTestBase { * * @var array */ - public static $modules = array('node'); + public static $modules = array('node', 'contextual'); protected function setUp() { parent::setUp(); diff --git a/core/modules/rest/src/Tests/DeleteTest.php b/core/modules/rest/src/Tests/DeleteTest.php index 402325c..6456df1 100644 --- a/core/modules/rest/src/Tests/DeleteTest.php +++ b/core/modules/rest/src/Tests/DeleteTest.php @@ -55,7 +55,7 @@ public function testDelete() { // Try to delete an entity that does not exist. $response = $this->httpRequest($entity_type . '/9999', 'DELETE'); $this->assertResponse(404); - $this->assertText('The requested page "/' . $entity_type . '/9999" could not be found.'); + $this->assertText('The requested page could not be found.'); // Try to delete an entity without proper permissions. $this->drupalLogout(); diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module index 3768b90..5471d7e 100644 --- a/core/modules/simpletest/simpletest.module +++ b/core/modules/simpletest/simpletest.module @@ -1,7 +1,6 @@ fragmentRenderer = $html_fragment_renderer; - $this->titleResolver = $title_resolver; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('html_fragment_renderer'), - $container->get('title_resolver') - ); - } +class BatchController { /** * Returns a system batch page. @@ -64,9 +22,8 @@ public static function create(ContainerInterface $container) { * @param \Symfony\Component\HttpFoundation\Request $request * The current request object. * - * @return mixed - * A \Symfony\Component\HttpFoundation\Response object or page element or - * NULL. + * @return \Symfony\Component\HttpFoundation\Response|array + * A \Symfony\Component\HttpFoundation\Response object or render array. * * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException */ @@ -81,46 +38,13 @@ public function batchPage(Request $request) { return $output; } elseif (isset($output)) { - // Force a page without blocks or messages to - // display a list of collected messages later. - drupal_set_page_content($output); - $page = element_info('page'); - $page['#show_messages'] = FALSE; - - $page = $this->render($page); - + $page = [ + '#type' => 'page', + '#show_messages' => FALSE, + 'content' => $output, + ]; return $page; } } - /** - * {@inheritdoc} - */ - public function render(array $output, $status_code = 200) { - if (!isset($output['#title'])) { - $output['#title'] = $this->titleResolver->getTitle(\Drupal::request(), \Drupal::routeMatch()->getRouteObject()); - } - $page = new HtmlPage('', isset($output['#cache']) ? $output['#cache'] : array(), $output['#title']); - - $page_array = drupal_prepare_page($output); - - $page = $this->fragmentRenderer->preparePage($page, $page_array); - - $page->setBodyTop(drupal_render_root($page_array['page_top'])); - $page->setBodyBottom(drupal_render_root($page_array['page_bottom'])); - $page->setContent(drupal_render_root($page_array)); - - drupal_process_attached($page_array); - if (isset($page_array['page_top'])) { - drupal_process_attached($page_array['page_top']); - } - if (isset($page_array['page_bottom'])) { - drupal_process_attached($page_array['page_bottom']); - } - - $page->setStatusCode($status_code); - - return $page; - } - } diff --git a/core/modules/system/src/Controller/DbUpdateController.php b/core/modules/system/src/Controller/DbUpdateController.php index 7dff04a..2534301 100644 --- a/core/modules/system/src/Controller/DbUpdateController.php +++ b/core/modules/system/src/Controller/DbUpdateController.php @@ -12,7 +12,7 @@ use Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface; -use Drupal\Core\Page\DefaultHtmlPageRenderer; +use Drupal\Core\Render\BareHtmlPageRendererInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Site\Settings; use Drupal\Core\State\StateInterface; @@ -69,6 +69,13 @@ class DbUpdateController extends ControllerBase { protected $entityDefinitionUpdateManager; /** + * The bare HTML page renderer. + * + * @var \Drupal\Core\Render\BareHtmlPageRendererInterface + */ + protected $bareHtmlPageRenderer; + + /** * Constructs a new UpdateController. * * @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface $key_value_expirable_factory @@ -83,14 +90,17 @@ class DbUpdateController extends ControllerBase { * The current user. * @param \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface $entity_definition_update_manager * The entity definition update manager. + * @param \Drupal\Core\Render\BareHtmlPageRendererInterface $bare_html_page_renderer + * The bare HTML page renderer. */ - public function __construct(KeyValueExpirableFactoryInterface $key_value_expirable_factory, CacheBackendInterface $cache, StateInterface $state, ModuleHandlerInterface $module_handler, AccountInterface $account, EntityDefinitionUpdateManagerInterface $entity_definition_update_manager) { + public function __construct(KeyValueExpirableFactoryInterface $key_value_expirable_factory, CacheBackendInterface $cache, StateInterface $state, ModuleHandlerInterface $module_handler, AccountInterface $account, EntityDefinitionUpdateManagerInterface $entity_definition_update_manager, BareHtmlPageRendererInterface $bare_html_page_renderer) { $this->keyValueExpirableFactory = $key_value_expirable_factory; $this->cache = $cache; $this->state = $state; $this->moduleHandler = $module_handler; $this->account = $account; $this->entityDefinitionUpdateManager = $entity_definition_update_manager; + $this->bareHtmlPageRenderer = $bare_html_page_renderer; } /** @@ -103,7 +113,8 @@ public static function create(ContainerInterface $container) { $container->get('state'), $container->get('module_handler'), $container->get('current_user'), - $container->get('entity.definition_update_manager') + $container->get('entity.definition_update_manager'), + $container->get('bare_html_page_renderer') ); } @@ -176,7 +187,7 @@ public function handle($op, Request $request) { } $title = isset($output['#title']) ? $output['#title'] : $this->t('Drupal database update'); - return new Response(DefaultHtmlPageRenderer::renderPage($output, $title, 'maintenance', $regions)); + return new Response($this->bareHtmlPageRenderer->renderMaintenancePage($output, $title, $regions)); } /** diff --git a/core/modules/system/src/Controller/Http4xxController.php b/core/modules/system/src/Controller/Http4xxController.php new file mode 100644 index 0000000..7add79e --- /dev/null +++ b/core/modules/system/src/Controller/Http4xxController.php @@ -0,0 +1,41 @@ + $this->t('You are not authorized to access this page.'), + ]; + } + + /** + * The default 404 content. + * + * @return array + * A render array containing the message to display for 404 pages. + */ + public function on404() { + return [ + '#markup' => $this->t('The requested page could not be found.'), + ]; + } + +} diff --git a/core/modules/system/src/Event/PageDisplayVariantSelectionEvent.php b/core/modules/system/src/Event/PageDisplayVariantSelectionEvent.php new file mode 100644 index 0000000..9177a18 --- /dev/null +++ b/core/modules/system/src/Event/PageDisplayVariantSelectionEvent.php @@ -0,0 +1,55 @@ +pluginId = $plugin_id; + } + + /** + * The selected page display variant plugin ID. + * + * @param string $plugin_id + * The ID of the page display variant plugin to use. + */ + public function setPluginId($plugin_id) { + $this->pluginId = $plugin_id; + } + + /** + * The selected page display variant plugin ID. + * + * @return string; + */ + public function getPluginId() { + return $this->pluginId; + } + +} diff --git a/core/modules/system/src/Event/SystemEvents.php b/core/modules/system/src/Event/SystemEvents.php new file mode 100644 index 0000000..a99eb59 --- /dev/null +++ b/core/modules/system/src/Event/SystemEvents.php @@ -0,0 +1,23 @@ +mainContent = $main_content; + } /** * {@inheritdoc} */ public function build() { - return array( - drupal_set_page_content() - ); + return $this->mainContent; } /** diff --git a/core/modules/system/src/Plugin/DisplayVariant/SimplePageVariant.php b/core/modules/system/src/Plugin/DisplayVariant/SimplePageVariant.php new file mode 100644 index 0000000..afe298a --- /dev/null +++ b/core/modules/system/src/Plugin/DisplayVariant/SimplePageVariant.php @@ -0,0 +1,47 @@ +mainContent = $main_content; + } + + /** + * {@inheritdoc} + */ + public function build() { + $build = [ + 'content' => $this->mainContent, + ]; + return $build; + } + +} diff --git a/core/modules/system/src/Tests/Common/AddFeedTest.php b/core/modules/system/src/Tests/Common/AddFeedTest.php index ba55937..0f5a560 100644 --- a/core/modules/system/src/Tests/Common/AddFeedTest.php +++ b/core/modules/system/src/Tests/Common/AddFeedTest.php @@ -7,8 +7,6 @@ namespace Drupal\system\Tests\Common; -use Drupal\Core\Page\FeedLinkElement; -use Drupal\Core\Page\HtmlPage; use Drupal\simpletest\WebTestBase; /** @@ -30,10 +28,6 @@ function testBasicFeedAddNoTitle() { $external_for_title = 'http://' . $this->randomMachineName(12) . '/' . $this->randomMachineName(12); $fully_qualified_for_title = _url($this->randomMachineName(12), array('absolute' => TRUE)); - // Possible permutations of _drupal_add_feed() to test. - // - 'input_url': the path passed to _drupal_add_feed(), - // - 'output_url': the expected URL to be found in the header. - // - 'title' == the title of the feed as passed into _drupal_add_feed(). $urls = array( 'path without title' => array( 'url' => _url($path, array('absolute' => TRUE)), @@ -61,14 +55,14 @@ function testBasicFeedAddNoTitle() { ), ); - $html_page = new HtmlPage(); - + $build = []; foreach ($urls as $feed_info) { - $feed_link = new FeedLinkElement($feed_info['title'], $feed_info['url']); - $html_page->addLinkElement($feed_link); + $build['#attached']['feed'][] = [$feed_info['url'], $feed_info['title']]; } - $this->drupalSetContent(\Drupal::service('html_page_renderer')->render($html_page)); + drupal_process_attached($build); + + $this->drupalSetContent(drupal_get_html_head()); foreach ($urls as $description => $feed_info) { $this->assertPattern($this->urlToRSSLinkPattern($feed_info['url'], $feed_info['title']), format_string('Found correct feed header for %description', array('%description' => $description))); } @@ -80,7 +74,7 @@ function testBasicFeedAddNoTitle() { function urlToRSSLinkPattern($url, $title = '') { // Escape any regular expression characters in the URL ('?' is the worst). $url = preg_replace('/([+?.*])/', '[$0]', $url); - $generated_pattern = '%%'; + $generated_pattern = '%%'; return $generated_pattern; } diff --git a/core/modules/system/src/Tests/Common/PageRenderTest.php b/core/modules/system/src/Tests/Common/PageRenderTest.php index 09dd9b3..b270170 100644 --- a/core/modules/system/src/Tests/Common/PageRenderTest.php +++ b/core/modules/system/src/Tests/Common/PageRenderTest.php @@ -7,6 +7,7 @@ namespace Drupal\system\Tests\Common; +use Drupal\Core\Controller\HtmlController; use Drupal\simpletest\KernelTestBase; /** @@ -61,14 +62,14 @@ function testHookPageAttachmentsAlter() { function assertPageRenderHookExceptions($module, $hook) { // Assert a valid hook implementation doesn't trigger an exception. $page = []; - drupal_prepare_page($page); + HtmlController::invokePageAttachmentHooks($page); // Assert an invalid hook implementation doesn't trigger an exception. \Drupal::state()->set($module . '.' . $hook . '.descendant_attached', TRUE); $assertion = $hook . '() implementation that sets #attached on a descendant triggers an exception'; $page = []; try { - drupal_prepare_page($page); + HtmlController::invokePageAttachmentHooks($page); $this->error($assertion); } catch (\LogicException $e) { @@ -82,7 +83,7 @@ function assertPageRenderHookExceptions($module, $hook) { $assertion = $hook . '() implementation that sets a child render array triggers an exception'; $page = []; try { - drupal_prepare_page($page); + HtmlController::invokePageAttachmentHooks($page); $this->error($assertion); } catch (\LogicException $e) { diff --git a/core/modules/system/src/Tests/Routing/ExceptionHandlingTest.php b/core/modules/system/src/Tests/Routing/ExceptionHandlingTest.php index b6ed513..0d925b7 100644 --- a/core/modules/system/src/Tests/Routing/ExceptionHandlingTest.php +++ b/core/modules/system/src/Tests/Routing/ExceptionHandlingTest.php @@ -77,14 +77,14 @@ public function testHtml403() { /** @var \Symfony\Component\HttpKernel\HttpKernelInterface $kernel */ $kernel = \Drupal::getContainer()->get('http_kernel'); - $response = $kernel->handle($request); + $response = $kernel->handle($request)->prepare($request); $this->assertEqual($response->getStatusCode(), Response::HTTP_FORBIDDEN); - $this->assertEqual($response->headers->get('Content-type'), 'text/html'); + $this->assertEqual($response->headers->get('Content-type'), 'text/html; charset=UTF-8'); } /** - * Tests the exception handling for HTML and 403 status code. + * Tests the exception handling for HTML and 404 status code. */ public function testHtml404() { $request = Request::create('/not-found'); @@ -93,27 +93,10 @@ public function testHtml404() { /** @var \Symfony\Component\HttpKernel\HttpKernelInterface $kernel */ $kernel = \Drupal::getContainer()->get('http_kernel'); - $response = $kernel->handle($request); + $response = $kernel->handle($request)->prepare($request); $this->assertEqual($response->getStatusCode(), Response::HTTP_NOT_FOUND); - $this->assertEqual($response->headers->get('Content-type'), 'text/html'); - } - - /** - * Tests the exception handling for HTML and 405 status code. - */ - public function testHtml405() { - $request = Request::create('/admin', 'NOT_EXISTING'); - $request->headers->set('Accept', 'text/html'); - $request->setFormat('html', ['text/html']); - - /** @var \Symfony\Component\HttpKernel\HttpKernelInterface $kernel */ - $kernel = \Drupal::getContainer()->get('http_kernel'); - $response = $kernel->handle($request); - - $this->assertEqual($response->getStatusCode(), Response::HTTP_METHOD_NOT_ALLOWED); - $this->assertEqual($response->headers->get('Content-type'), 'text/html'); - $this->assertEqual($response->getContent(), 'Method Not Allowed'); + $this->assertEqual($response->headers->get('Content-type'), 'text/html; charset=UTF-8'); } } diff --git a/core/modules/system/src/Tests/System/MainContentFallbackTest.php b/core/modules/system/src/Tests/System/MainContentFallbackTest.php deleted file mode 100644 index 288e681..0000000 diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml index b285bcf..26317f6 100644 --- a/core/modules/system/system.routing.yml +++ b/core/modules/system/system.routing.yml @@ -7,6 +7,22 @@ system.ajax: requirements: _access: 'TRUE' +system.403: + path: '/system/403' + defaults: + _content: '\Drupal\system\Controller\Http4xxController:on403' + _title: 'Access denied' + requirements: + _access: 'TRUE' + +system.404: + path: '/system/404' + defaults: + _content: '\Drupal\system\Controller\Http4xxController:on404' + _title: 'Page not found' + requirements: + _access: 'TRUE' + system.admin: path: '/admin' defaults: @@ -403,13 +419,13 @@ system.admin_config: requirements: _permission: 'access administration pages' -# @todo system.batch_page.html does not work due to some ordering issues. -system.batch_page.normal: +system.batch_page.html: path: '/batch' defaults: _content: '\Drupal\system\Controller\BatchController::batchPage' requirements: _access: 'TRUE' + _format: 'html' options: _admin_route: TRUE diff --git a/core/modules/system/templates/html.html.twig b/core/modules/system/templates/html.html.twig index d605532..8e9cf40 100644 --- a/core/modules/system/templates/html.html.twig +++ b/core/modules/system/templates/html.html.twig @@ -29,18 +29,18 @@ - {{ page.head }} + {{ html.head }} {{ head_title }} - {{ page.styles }} - {{ page.scripts }} + {{ html.styles }} + {{ html.scripts }} - {{ page_top }} - {{ page.content }} - {{ page_bottom }} - {{ page.scripts('footer') }} + {{ html.page_top }} + {{ html.page }} + {{ html.page_bottom }} + {{ html.scripts_bottom }} diff --git a/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module b/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module index 9a7fb8a..2ecbe2a 100644 --- a/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module +++ b/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module @@ -194,7 +194,7 @@ function ajax_forms_test_advanced_commands_settings_with_merging_callback($form, function ajax_forms_test_validation_form_callback($form, FormStateInterface $form_state) { drupal_set_message("ajax_forms_test_validation_form_callback invoked"); drupal_set_message(t("Callback: drivertext=%drivertext, spare_required_field=%spare_required_field", array('%drivertext' => $form_state->getValue('drivertext'), '%spare_required_field' => $form_state->getValue('spare_required_field')))); - return '
ajax_forms_test_validation_form_callback at ' . date('c') . '
'; + return ['#markup' => '
ajax_forms_test_validation_form_callback at ' . date('c') . '
']; } /** @@ -203,7 +203,7 @@ function ajax_forms_test_validation_form_callback($form, FormStateInterface $for function ajax_forms_test_validation_number_form_callback($form, FormStateInterface $form_state) { drupal_set_message("ajax_forms_test_validation_number_form_callback invoked"); drupal_set_message(t("Callback: drivernumber=%drivernumber, spare_required_field=%spare_required_field", array('%drivernumber' => $form_state->getValue('drivernumber'), '%spare_required_field' => $form_state->getValue('spare_required_field')))); - return '
ajax_forms_test_validation_number_form_callback at ' . date('c') . '
'; + return ['#markup' => '
ajax_forms_test_validation_number_form_callback at ' . date('c') . '
']; } /** diff --git a/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php b/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php index 75fa1c1..00619ef 100644 --- a/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php +++ b/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php @@ -88,7 +88,7 @@ public function testEdit(Request $request, $entity_type_id) { * @see \Drupal\entity_test\Routing\EntityTestRoutes::routes() */ public function testAdmin() { - return ''; + return []; } /** diff --git a/core/modules/system/tests/modules/error_test/src/Controller/ErrorTestController.php b/core/modules/system/tests/modules/error_test/src/Controller/ErrorTestController.php index 7a4bf90..8ba584a 100644 --- a/core/modules/system/tests/modules/error_test/src/Controller/ErrorTestController.php +++ b/core/modules/system/tests/modules/error_test/src/Controller/ErrorTestController.php @@ -53,7 +53,7 @@ public function generateWarnings($collect_errors = FALSE) { $awesomely_big = 1/0; // This will generate a user error. trigger_error("Drupal is awesome", E_USER_WARNING); - return ""; + return []; } /** diff --git a/core/modules/system/tests/modules/menu_test/menu_test.module b/core/modules/system/tests/modules/menu_test/menu_test.module index a5ce74e..a87983b 100644 --- a/core/modules/system/tests/modules/menu_test/menu_test.module +++ b/core/modules/system/tests/modules/menu_test/menu_test.module @@ -91,7 +91,7 @@ function menu_test_menu_local_tasks_alter(&$data, $route_name) { * @deprecated Use \Drupal\menu_test\Controller\MenuTestController::menuTestCallback() */ function menu_test_callback() { - return 'This is menu_test_callback().'; + return ['#markup' => 'This is menu_test_callback().']; } /** @@ -117,7 +117,7 @@ function menu_test_theme_page_callback($inherited = FALSE) { if ($inherited) { $output .= ' Theme negotiation inheritance is being tested.'; } - return $output; + return ['#markup' => $output]; } /** diff --git a/core/modules/system/tests/modules/menu_test/src/TestControllers.php b/core/modules/system/tests/modules/menu_test/src/TestControllers.php index 608e0c7..bbc4f1f 100644 --- a/core/modules/system/tests/modules/menu_test/src/TestControllers.php +++ b/core/modules/system/tests/modules/menu_test/src/TestControllers.php @@ -19,28 +19,28 @@ class TestControllers { * Returns page to be used as a login path. */ public function testLogin() { - return 'This is TestControllers::testLogin.'; + return ['#markup' => 'This is TestControllers::testLogin.']; } /** * Prints out test data. */ public function test1() { - return 'test1'; + return ['#markup' => 'test1']; } /** * Prints out test data. */ public function test2() { - return 'test2'; + return ['#markup' => 'test2']; } /** * Prints out test data. */ public function testDerived() { - return 'testDerived'; + return ['#markup' => 'testDerived']; } /** @@ -54,10 +54,10 @@ public function testDerived() { */ public function testDefaults($placeholder = NULL) { if ($placeholder) { - return String::format("Sometimes there is a placeholder: '@placeholder'.", array('@placeholder' => $placeholder)); + return ['#markup' => String::format("Sometimes there is a placeholder: '@placeholder'.", array('@placeholder' => $placeholder))]; } else { - return String::format('Sometimes there is no placeholder.'); + return ['#markup' => String::format('Sometimes there is no placeholder.')]; } } diff --git a/core/modules/system/tests/modules/module_test/src/Controller/ModuleTestController.php b/core/modules/system/tests/modules/module_test/src/Controller/ModuleTestController.php index df3b8f7..3a2532e 100644 --- a/core/modules/system/tests/modules/module_test/src/Controller/ModuleTestController.php +++ b/core/modules/system/tests/modules/module_test/src/Controller/ModuleTestController.php @@ -30,7 +30,7 @@ public function hookDynamicLoadingInvokeAll() { * @todo Remove module_test_class_loading(). */ public function testClassLoading() { - return module_test_class_loading(); + return ['#markup' => module_test_class_loading()]; } } diff --git a/core/modules/system/tests/modules/paramconverter_test/src/TestControllers.php b/core/modules/system/tests/modules/paramconverter_test/src/TestControllers.php index 8b48b45..33f52fc 100644 --- a/core/modules/system/tests/modules/paramconverter_test/src/TestControllers.php +++ b/core/modules/system/tests/modules/paramconverter_test/src/TestControllers.php @@ -19,14 +19,14 @@ class TestControllers { public function testUserNodeFoo(EntityInterface $user, NodeInterface $node, Request $request) { $foo = $request->attributes->get('foo'); $foo = is_object($foo) ? $foo->label() : $foo; - return "user: {$user->label()}, node: {$node->label()}, foo: $foo"; + return ['#markup' => "user: {$user->label()}, node: {$node->label()}, foo: $foo"]; } public function testNodeSetParent(NodeInterface $node, NodeInterface $parent) { - return "Setting '{$parent->label()}' as parent of '{$node->label()}'."; + return ['#markup' => "Setting '{$parent->label()}' as parent of '{$node->label()}'."]; } public function testEntityLanguage(NodeInterface $node) { - return $node->label(); + return ['#markup' => $node->label()]; } } diff --git a/core/modules/system/tests/modules/router_test_directory/src/TestContent.php b/core/modules/system/tests/modules/router_test_directory/src/TestContent.php index 6559c70..dc2caca 100644 --- a/core/modules/system/tests/modules/router_test_directory/src/TestContent.php +++ b/core/modules/system/tests/modules/router_test_directory/src/TestContent.php @@ -43,7 +43,7 @@ public static function create(ContainerInterface $container) { * Provides example content for testing route enhancers. */ public function test1() { - return 'abcde'; + return ['#markup' => 'abcde']; } /** @@ -54,13 +54,13 @@ public function test1() { */ public function test11() { $account = $this->currentUser(); - return $account->getUsername(); + return ['#markup' => $account->getUsername()]; } public function testAccount(UserInterface $user) { $current_user_name = $this->currentUser()->getUsername(); $this->currentUser()->setAccount($user); - return $current_user_name . ':' . $user->getUsername(); + return ['#markup' => $current_user_name . ':' . $user->getUsername()]; } /** diff --git a/core/modules/system/tests/modules/router_test_directory/src/TestControllers.php b/core/modules/system/tests/modules/router_test_directory/src/TestControllers.php index 1eec263..10ba2ca 100644 --- a/core/modules/system/tests/modules/router_test_directory/src/TestControllers.php +++ b/core/modules/system/tests/modules/router_test_directory/src/TestControllers.php @@ -26,19 +26,19 @@ public function test1() { } public function test2() { - return "test2"; + return ['#markup' => "test2"]; } public function test3($value) { - return $value; + return ['#markup' => $value]; } public function test4($value) { - return $value; + return ['#markup' => $value]; } public function test5() { - return "test5"; + return ['#markup' => "test5"]; } public function test6() { diff --git a/core/modules/system/tests/modules/session_test/src/Controller/SessionTestController.php b/core/modules/system/tests/modules/session_test/src/Controller/SessionTestController.php index 5fbd412..5993056 100644 --- a/core/modules/system/tests/modules/session_test/src/Controller/SessionTestController.php +++ b/core/modules/system/tests/modules/session_test/src/Controller/SessionTestController.php @@ -24,8 +24,8 @@ class SessionTestController extends ControllerBase { */ public function get() { return empty($_SESSION['session_test_value']) - ? "" - : $this->t('The current value of the stored session variable is: %val', array('%val' => $_SESSION['session_test_value'])); + ? [] + : ['#markup' => $this->t('The current value of the stored session variable is: %val', array('%val' => $_SESSION['session_test_value']))]; } /** @@ -41,7 +41,7 @@ public function getId() { \Drupal::service('session_manager')->save(); - return 'session_id:' . session_id() . "\n"; + return ['#markup' => 'session_id:' . session_id() . "\n"]; } /** @@ -54,7 +54,7 @@ public function getId() { * A notification message with session ID. */ public function getIdFromCookie(Request $request) { - return 'session_id:' . $request->cookies->get(session_name()) . "\n"; + return ['#markup' => 'session_id:' . $request->cookies->get(session_name()) . "\n"]; } /** @@ -69,7 +69,7 @@ public function getIdFromCookie(Request $request) { public function set($test_value) { $_SESSION['session_test_value'] = $test_value; - return $this->t('The current value of the stored session variable has been set to %val', array('%val' => $test_value)); + return ['#markup' => $this->t('The current value of the stored session variable has been set to %val', array('%val' => $test_value))]; } /** @@ -85,7 +85,7 @@ public function set($test_value) { public function noSet($test_value) { \Drupal::service('session_manager')->disable(); $this->set($test_value); - return $this->t('session saving was disabled, and then %val was set', array('%val' => $test_value)); + return ['#markup' => $this->t('session saving was disabled, and then %val was set', array('%val' => $test_value))]; } /** @@ -111,6 +111,7 @@ public function setMessage() { public function setMessageButDontSave() { \Drupal::service('session_manager')->disable(); $this->setMessage(); + return ['#markup' => '']; } /** @@ -124,6 +125,7 @@ public function setNotStarted() { if (!drupal_session_will_start()) { $this->set($this->t('Session was not started')); } + return ['#markup' => '']; } /** @@ -133,6 +135,6 @@ public function setNotStarted() { * A notification message. */ public function isLoggedIn() { - return $this->t('User is logged in.'); + return ['#markup' => $this->t('User is logged in.')]; } } diff --git a/core/modules/system/tests/modules/system_module_test/src/EventSubscriber/HtmlPageSubscriber.php b/core/modules/system/tests/modules/system_module_test/src/EventSubscriber/HtmlPageSubscriber.php deleted file mode 100644 index f64cd54..0000000 diff --git a/core/modules/system/tests/modules/system_module_test/system_module_test.module b/core/modules/system/tests/modules/system_module_test/system_module_test.module index 8d67e7a..4d5e073 100644 --- a/core/modules/system/tests/modules/system_module_test/system_module_test.module +++ b/core/modules/system/tests/modules/system_module_test/system_module_test.module @@ -5,3 +5,23 @@ * Provides System module hook implementations for testing purposes. */ +/** + * Implements hook_element_info_alter(). + */ +function system_module_test_element_info_alter(&$types) { + $types['html']['#pre_render'][] = 'system_module_test_html_pre_render'; +} + +/** + * Additional #pre_render callback for 'html' elements. + */ +function system_module_test_html_pre_render(array $element) { + // Remove the HTML5 mobile meta-tags. + $meta_tags_to_remove = ['MobileOptimized', 'HandheldFriendly', 'viewport', 'cleartype']; + foreach ($element['#attached']['html_head'] as $index => $parts) { + if (in_array($parts[1], $meta_tags_to_remove)) { + unset($element['#attached']['html_head'][$index]); + } + } + return $element; +} diff --git a/core/modules/system/tests/modules/system_module_test/system_module_test.services.yml b/core/modules/system/tests/modules/system_module_test/system_module_test.services.yml deleted file mode 100644 index 3abeb5e..0000000 diff --git a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php index c6b6903..9906b10 100644 --- a/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php +++ b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php @@ -49,7 +49,7 @@ public static function create(ContainerInterface $container) { * The text to display. */ public function mainContentFallback() { - return $this->t('Content to test main content fallback'); + return ['#markup' => $this->t('Content to test main content fallback')]; } /** @@ -65,7 +65,7 @@ public function drupalSetMessageTest() { // Remove the first. unset($_SESSION['messages']['status'][0]); - return ''; + return []; } /** @@ -93,10 +93,10 @@ public function lockExit() { */ public function lockPersist($lock_name) { if ($this->persistentLock->acquire($lock_name)) { - return 'TRUE: Lock successfully acquired in SystemTestController::lockPersist()'; + return ['#markup' => 'TRUE: Lock successfully acquired in SystemTestController::lockPersist()']; } else { - return 'FALSE: Lock not acquired in SystemTestController::lockPersist()'; + return ['#markup' => 'FALSE: Lock not acquired in SystemTestController::lockPersist()']; } } diff --git a/core/modules/system/tests/modules/system_test/system_test.module b/core/modules/system/tests/modules/system_test/system_test.module index 78c1504..b44e0a0 100644 --- a/core/modules/system/tests/modules/system_test/system_test.module +++ b/core/modules/system/tests/modules/system_test/system_test.module @@ -72,10 +72,10 @@ function system_test_system_info_alter(&$info, Extension $file, $type) { function system_test_lock_acquire() { if (\Drupal::lock()->acquire('system_test_lock_acquire')) { \Drupal::lock()->release('system_test_lock_acquire'); - return 'TRUE: Lock successfully acquired in system_test_lock_acquire()'; + return ['#markup' => 'TRUE: Lock successfully acquired in system_test_lock_acquire()']; } else { - return 'FALSE: Lock not acquired in system_test_lock_acquire()'; + return ['#markup' => 'FALSE: Lock not acquired in system_test_lock_acquire()']; } } @@ -91,7 +91,7 @@ function system_test_lock_exit() { exit(); } else { - return 'FALSE: Lock not acquired in system_test_lock_exit()'; + return ['#markup' => 'FALSE: Lock not acquired in system_test_lock_exit()']; } } @@ -99,19 +99,6 @@ function system_test_lock_exit() { * Implements hook_page_attachments(). */ function system_test_page_attachments(array &$page) { - $menu_item['path'] = current_path(); - $main_content_display = &drupal_static('system_main_content_added', FALSE); - - if ($menu_item['path'] == 'system-test/main-content-fallback') { - // Get the main content, to e.g. dynamically attach an asset. - drupal_set_page_content(); - // Indicate we don't want to override the main content. - $main_content_display = FALSE; - } - elseif ($menu_item['path'] == 'system-test/main-content-handling') { - // Set the main content. - drupal_set_page_content('
Overridden!
'); - } // Used by FrontPageTestCase to get the results of drupal_is_front_page(). $frontpage = \Drupal::state()->get('system_test.front_page_output') ?: 0; if ($frontpage && drupal_is_front_page()) { diff --git a/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php b/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php index caf9533..fb8e1f9 100644 --- a/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php +++ b/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php @@ -56,7 +56,7 @@ public function testInfoStylesheets() { * A render array containing a theme override. */ public function testTemplate() { - return \Drupal::theme()->render('theme_test_template_test', array()); + return ['#markup' => \Drupal::theme()->render('theme_test_template_test', array())]; } /** @@ -82,7 +82,7 @@ public function testInlineTemplate() { * An HTML string containing the themed output. */ public function testSuggestion() { - return \Drupal::theme()->render(array('theme_test__suggestion', 'theme_test'), array()); + return ['#markup' => \Drupal::theme()->render(array('theme_test__suggestion', 'theme_test'), array())]; } /** @@ -92,7 +92,7 @@ public function testSuggestion() { * Content in theme_test_output GLOBAL. */ public function testRequestListener() { - return $GLOBALS['theme_test_output']; + return ['#markup' => $GLOBALS['theme_test_output']]; } /** diff --git a/core/modules/system/tests/modules/twig_theme_test/src/TwigThemeTestController.php b/core/modules/system/tests/modules/twig_theme_test/src/TwigThemeTestController.php index 9441a50..f657c4d 100644 --- a/core/modules/system/tests/modules/twig_theme_test/src/TwigThemeTestController.php +++ b/core/modules/system/tests/modules/twig_theme_test/src/TwigThemeTestController.php @@ -18,7 +18,7 @@ class TwigThemeTestController { * Menu callback for testing PHP variables in a Twig template. */ public function phpVariablesRender() { - return \Drupal::theme()->render('twig_theme_test_php_variables', array()); + return ['#markup' => \Drupal::theme()->render('twig_theme_test_php_variables', array())]; } /** diff --git a/core/modules/system/theme.api.php b/core/modules/system/theme.api.php index fba946f..bd52ba9 100644 --- a/core/modules/system/theme.api.php +++ b/core/modules/system/theme.api.php @@ -302,6 +302,67 @@ * * See drupal_process_attached() for additional information. * + * @section render_pipeline The Render Pipeline (or: how Drupal renders pages) + * + * First, you need to know the general routing concepts: please read + * @ref sec_controller first. + * + * A _controller route expects the controller to return a Response. + * + * A non-_controller (typically _content) route expects the controller to return + * the "main content", as a render array. That main content may be requested and + * rendered in multiple ways: it can be rendered in a certain format (HTML, JSON + * …) and/or in a certain decorated manner (e.g. with blocks around the main + * content). + * Therefore the first step is to negotiate a format + * (\Drupal\Core\EventSubscriber\ContentControllerSubscriber::onRequestDeriveFormat()) + * which will determine which "main content controller" (which implementation of + * \Drupal\Core\Controller\MainContentControllerInterface) to use. The selected + * main content controller will be set on the request as the _controller to use + * (just like a "regular" _controller route attribute). + * + * Having negotiated a _controller, we now go to the three stages of main + * content controllers: + * 1. getMainContent(): get the main content from the (_content) sub-controller, + * this must always be a render array + * 2. prepareContent(): apply any preparations/transformations + * 3. renderContentIntoResponse(): turn the content render array into a response + * + * These same steps are applied regardless of which main content controller is + * selected: it's the same for the AJAX, HTML, Drupal Dialog and Drupal Modal + * Dialog controllers. + * + * Specific main content controllers may of course implement additional + * flexibility if they want to. The default HTML controller does this, for + * example. And since rendering HTML pages is the most common use for Drupal, + * this will cover that also. + * + * \Drupal\Core\Controller\HtmlController::prepareContent() looks at the render + * array it receives. If it's already #type 'page', then most of the work it + * should do is already done. After all, if a #type 'page' is indicated to be + * the main content, then that implies no decorations should be applied, since + * it already represents the final for the HTML document. + * If it's not yet #type 'page', however, then we need to build that still. The + * SystemEvents::SELECT_PAGE_DISPLAY_VARIANT event is dispatched, to select a + * page display variant. By default, SimplePageVariant is used, which doesn't do + * any decorating. But when Block module is enabled, BlockPageVariant is used, + * which allows the site builder to place blocks in any of the page regions, and + * hence "decorate" the main content. + * The outcome of \Drupal\Core\Controller\HtmlController::prepareContent() is + * always a #type 'page' render array. This is considered the "actual" content + * for HTML responses. + * + * \Drupal\Core\Controller\HtmlController::renderContentIntoResponse() — the + * third and final step — then wraps the #type 'page' (which represents + * page.html.twig) in a #type 'html' render array (which represents + * html.html.twig), to then render the entire HTML document. + * + * For HTML pages to be rendered in limited environments, such as when you are + * installing or updating Drupal, or when you put it in maintenance mode, or + * when an error occurs, a simpler HTML page renderer is used for rendering + * these bare pages: \Drupal\Core\Render\BareHtmlPageRenderer + * + * * @see themeable * * @} diff --git a/core/modules/views/views.module b/core/modules/views/views.module index 02e9f52..ecd2cf2 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -306,7 +306,7 @@ function views_page_display_pre_render(array $element) { /** * Implements MODULE_preprocess_HOOK(). */ -function views_preprocess_page(&$variables) { +function views_preprocess_html(&$variables) { // Early-return to prevent adding unnecessary JavaScript. if (!\Drupal::currentUser()->hasPermission('access contextual links')) { return; @@ -325,20 +325,8 @@ function views_preprocess_page(&$variables) { // page.html.twig, so we can only find it using JavaScript. We therefore // remove the "contextual-region" class from the tag here and add // JavaScript that will insert it back in the correct place. - if (!empty($variables['page']['#views_contextual_links']) && isset($variables['attributes']['class'])) { - /** @var \Drupal\Core\Page\HtmlPage $page_object */ - $page_object = $variables['page']['#page']; - $attributes = $page_object->getBodyAttributes(); - $class = $attributes['class'] ?: array(); - - $key = array_search('contextual-region', $variables['attributes']['class'] instanceof AttributeArray ? $variables['attributes']['class']->value() : $variables['attributes']['class']); - if ($key !== FALSE) { - /** @var \Drupal\Core\Page\HtmlPage $page_object */ - unset($class[$key]); - $attributes['class'] = $class; - $attributes['data-views-page-contextual-id'] = $variables['title_suffix']['contextual_links']['#id']; - $variables['#attached']['library'][] = 'views/views.contextual-links'; - } + if (!empty($variables['html']['page']['#views_contextual_links'])) { + $variables['attributes']['data-views-page-contextual-id'] = _contextual_links_to_id($variables['html']['page']['#contextual_links']); } } @@ -460,6 +448,12 @@ function views_add_contextual_links(&$render_element, $location, ViewExecutable 'display_id' => $display_id, ), ); + // If we're setting contextual links on a page, for a page view, for a + // user that may use contextual links, attach Views' contextual links + // JavaScript. + if ($location === 'page' && $render_element['#type'] === 'page' && \Drupal::currentUser()->hasPermission('access contextual links')) { + $render_element['#attached']['library'][] = 'views/views.contextual-links'; + } } } } diff --git a/core/tests/Drupal/Tests/Core/Ajax/AjaxResponseRendererTest.php b/core/tests/Drupal/Tests/Core/Ajax/AjaxResponseRendererTest.php deleted file mode 100644 index 2c71d87..0000000 diff --git a/core/tests/Drupal/Tests/Core/Controller/AjaxControllerTest.php b/core/tests/Drupal/Tests/Core/Controller/AjaxControllerTest.php new file mode 100644 index 0000000..de8f094 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Controller/AjaxControllerTest.php @@ -0,0 +1,118 @@ +getMock('Drupal\Core\Controller\ControllerResolverInterface'); + $controller_resolver->expects($this->any()) + ->method('getArguments') + ->willReturn([]); + $element_info_manager = $this->getMock('Drupal\Core\Render\ElementInfoManagerInterface'); + $element_info_manager->expects($this->any()) + ->method('getInfo') + ->with('ajax') + ->willReturn([ + '#header' => TRUE, + '#commands' => array(), + '#error' => NULL, + ]); + $this->ajaxController = new TestAjaxController($controller_resolver, $element_info_manager); + } + + /** + * Tests the renderMainContent method. + * + * @covers \Drupal\Core\Controller\AjaxController::renderContentIntoResponse + */ + public function testRenderWithFragmentObject() { + $main_content = ['#markup' => 'example content']; + /** @var \Drupal\Core\Ajax\AjaxResponse $result */ + $result = $this->ajaxController->renderContentIntoResponse($main_content, '', []); + + $this->assertInstanceOf('Drupal\Core\Ajax\AjaxResponse', $result); + + $commands = $result->getCommands(); + $this->assertEquals('insert', $commands[0]['command']); + $this->assertEquals('example content', $commands[0]['data']); + + $this->assertEquals('insert', $commands[1]['command']); + $this->assertEquals('status_messages', $commands[1]['data']); + } + + /** + * Tests the handle method with a Json response object. + * + * @covers \Drupal\Core\Controller\AjaxController::handle + */ + public function testRenderWithResponseObject() { + $json_response = new JsonResponse(array('foo' => 'bar')); + $request = new Request(); + $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); + $_content = function() use ($json_response) { + return $json_response; + }; + $this->assertSame($json_response, $this->ajaxController->handle($request, $route_match, $_content)); + } + + /** + * Tests the handle method with an Ajax response object. + * + * @covers \Drupal\Core\Controller\AjaxController::handle + */ + public function testRenderWithAjaxResponseObject() { + $ajax_response = new AjaxResponse(array('foo' => 'bar')); + $request = new Request(); + $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); + $_content = function() use ($ajax_response) { + return $ajax_response; + }; + $this->assertSame($ajax_response, $this->ajaxController->handle($request, $route_match, $_content)); + } + +} + +class TestAjaxController extends AjaxController { + + /** + * {@inheritdoc} + */ + protected function drupalRender(&$elements, $is_recursive_call = FALSE) { + if (isset($elements['#markup'])) { + return $elements['#markup']; + } + elseif (isset($elements['#theme'])) { + return $elements['#theme']; + } + else { + return 'Markup'; + } + } + +} diff --git a/core/tests/Drupal/Tests/Core/Page/HtmlPageTest.php b/core/tests/Drupal/Tests/Core/Page/HtmlPageTest.php deleted file mode 100644 index f2305db..0000000 diff --git a/core/themes/bartik/bartik.theme b/core/themes/bartik/bartik.theme index 60f9a34..7588a8c 100644 --- a/core/themes/bartik/bartik.theme +++ b/core/themes/bartik/bartik.theme @@ -10,51 +10,49 @@ use Drupal\Core\Template\Attribute; /** - * Implements hook_preprocess_HOOK() for page.html.twig. + * Implements hook_preprocess_HOOK() for HTML document templates. * * Adds body classes if certain regions have content. */ -function bartik_preprocess_page(&$variables) { +function bartik_preprocess_html(&$variables) { // Add information about the number of sidebars. - /** @var \Drupal\Core\Page\HtmlPage $page_object */ - $page_object = $variables['page']['#page']; - $attributes = $page_object->getBodyAttributes(); - $classes = $attributes['class']; - if (!empty($variables['page']['sidebar_first']) && !empty($variables['page']['sidebar_second'])) { - $classes[] = 'layout-two-sidebars'; + if (!empty($variables['html']['page']['sidebar_first']) && !empty($variables['page']['sidebar_second'])) { + $variables['attributes']['class'][] = 'layout-two-sidebars'; } - elseif (!empty($variables['page']['sidebar_first'])) { - $classes[] = 'layout-one-sidebar'; - $classes[] = 'layout-sidebar-first'; + elseif (!empty($variables['html']['page']['sidebar_first'])) { + $variables['attributes']['class'][] = 'layout-one-sidebar'; + $variables['attributes']['class'][] = 'layout-sidebar-first'; } - elseif (!empty($variables['page']['sidebar_second'])) { - $classes[] = 'layout-one-sidebar'; - $classes[] = 'layout-sidebar-second'; + elseif (!empty($variables['html']['page']['sidebar_second'])) { + $variables['attributes']['class'][] = 'layout-one-sidebar'; + $variables['attributes']['class'][] = 'layout-sidebar-second'; } else { - $classes[] = 'layout-no-sidebars'; + $variables['attributes']['class'][] = 'layout-no-sidebars'; } - if (!empty($variables['page']['featured'])) { - $classes[] = 'featured'; + if (!empty($variables['html']['page']['featured'])) { + $variables['attributes']['class'][] = 'featured'; } - if (!empty($variables['page']['triptych_first']) - || !empty($variables['page']['triptych_middle']) - || !empty($variables['page']['triptych_last'])) { - $classes[] = 'triptych'; + if (!empty($variables['html']['page']['triptych_first']) + || !empty($variables['html']['page']['triptych_middle']) + || !empty($variables['html']['page']['triptych_last'])) { + $variables['attributes']['class'][] = 'triptych'; } - if (!empty($variables['page']['footer_firstcolumn']) - || !empty($variables['page']['footer_secondcolumn']) - || !empty($variables['page']['footer_thirdcolumn']) - || !empty($variables['page']['footer_fourthcolumn'])) { - $classes[] = 'footer-columns'; + if (!empty($variables['html']['page']['footer_firstcolumn']) + || !empty($variables['html']['page']['footer_secondcolumn']) + || !empty($variables['html']['page']['footer_thirdcolumn']) + || !empty($variables['html']['page']['footer_fourthcolumn'])) { + $variables['attributes']['class'][] = 'footer-columns'; } +} - // Store back the classes to the htmlpage object. - $attributes['class'] = $classes; - +/** + * Implements hook_preprocess_HOOK() for page templates. + */ +function bartik_preprocess_page(&$variables) { // Set the options that apply to both page and maintenance page. _bartik_process_page($variables); diff --git a/core/themes/seven/css/theme/install-page.css b/core/themes/seven/css/theme/install-page.css index c9d124d..395fb9b 100644 --- a/core/themes/seven/css/theme/install-page.css +++ b/core/themes/seven/css/theme/install-page.css @@ -5,7 +5,7 @@ * Unfortunately we have to make our styling quite strong, to override the * .maintenance-page styling. */ -.install-background { +.install-page { background-color: #1275b2; background-image: url(../../images/noise-low.png), diff --git a/core/themes/seven/css/theme/maintenance-page.css b/core/themes/seven/css/theme/maintenance-page.css index edf9018..f2f5a0e 100644 --- a/core/themes/seven/css/theme/maintenance-page.css +++ b/core/themes/seven/css/theme/maintenance-page.css @@ -2,7 +2,7 @@ * @file * Maintenance theming. */ -.maintenance-background { +body.maintenance-page { background-color: #e0e0d8; background-image: -webkit-radial-gradient(hsl(203, 2%, 90%), hsl(203, 2%, 95%)); background-image: radial-gradient(hsl(203, 2%, 90%), hsl(203, 2%, 95%)); diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme index 4863525..76ddfcc 100644 --- a/core/themes/seven/seven.theme +++ b/core/themes/seven/seven.theme @@ -10,24 +10,33 @@ use Drupal\Core\Form\FormStateInterface; /** - * Implements hook_preprocess_HOOK() for page templates. + * Implements hook_preprocess_HOOK() for HTML document templates. */ -function seven_preprocess_page(&$variables) { - /** @var \Drupal\Core\Page\HtmlPage $page_object */ - $page_object = $variables['page']['#page']; - $attributes = $page_object->getBodyAttributes(); - $classes = $attributes['class']; +function seven_preprocess_html(&$variables) { // Add information about the number of sidebars. - if (!empty($variables['page']['sidebar_first'])) { - $classes[] = 'layout-one-sidebar'; - $classes[] = 'layout-sidebar-first'; + $variables['attributes']['class'][] = 'one-sidebar'; + $variables['attributes']['class'][] = 'sidebar-first'; } else { - $classes[] = 'layout-no-sidebars'; + $variables['attributes']['class'][] = 'no-sidebars'; } - $attributes['class'] = $classes; + // If on a node add or edit page, add a node-layout class. + $path_args = explode('/', \Drupal::request()->getPathInfo()); + if ($suggestions = theme_get_suggestions($path_args, 'page', '-')) { + foreach ($suggestions as $suggestion) { + if ($suggestion === 'page-node-edit' || strpos($suggestion, 'page-node-add') !== FALSE) { + $variables['attributes']['class'][] = drupal_html_class('node-form-layout'); + } + } + } +} + +/** + * Implements hook_preprocess_HOOK() for page templates. + */ +function seven_preprocess_page(&$variables) { $variables['primary_local_tasks'] = $variables['tabs']; unset($variables['primary_local_tasks']['#secondary']); $variables['secondary_local_tasks'] = array( @@ -145,12 +154,6 @@ function seven_element_info_alter(&$type) { * Implements hook_preprocess_install_page(). */ function seven_preprocess_install_page(&$variables) { - $page_object = $variables['page']['#page']; - $attributes = $page_object->getHtmlAttributes(); - $classes = $attributes['class']; - $classes[] = 'install-background'; - $attributes['class'] = $classes; - // Seven has custom styling for the install page. $variables['#attached']['library'][] = 'seven/install-page'; } @@ -159,12 +162,6 @@ function seven_preprocess_install_page(&$variables) { * Implements hook_preprocess_maintenance_page(). */ function seven_preprocess_maintenance_page(&$variables) { - $page_object = $variables['page']['#page']; - $attributes = $page_object->getHtmlAttributes(); - $classes = $attributes['class']; - $classes[] = 'maintenance-background'; - $attributes['class'] = $classes; - // Seven has custom styling for the maintenance page. $variables['#attached']['library'][] = 'seven/maintenance-page'; } @@ -208,15 +205,3 @@ function seven_form_node_form_alter(&$form, FormStateInterface $form_state) { $form['revision_information']['#type'] = 'container'; $form['revision_information']['#group'] = 'meta'; } - -function seven_preprocess_html(&$variables) { - // If on a node add or edit page, add a node-layout class. - $path_args = explode('/', \Drupal::request()->getPathInfo()); - if ($suggestions = theme_get_suggestions($path_args, 'page', '-')) { - foreach ($suggestions as $suggestion) { - if ($suggestion === 'page-node-edit' || strpos($suggestion, 'page-node-add') !== FALSE) { - $variables['attributes']['class'][] = drupal_html_class('node-form-layout'); - } - } - } -}