core/authorize.php | 3 +- core/core.services.yml | 56 +++-- 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 | 82 ------ .../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 +++-- .../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 | 2 +- ...ageVariantTest.php => BlockPageVariantTest.php} | 115 ++++++--- .../modules/node/src/Tests/Views/FrontPageTest.php | 2 +- core/modules/simpletest/simpletest.module | 1 - .../system/src/Controller/BatchController.php | 92 +------ .../system/src/Controller/DbUpdateController.php | 19 +- .../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/System/MainContentFallbackTest.php | 85 ------- core/modules/system/system.routing.yml | 4 +- core/modules/system/templates/html.html.twig | 14 +- .../src/EventSubscriber/HtmlPageSubscriber.php | 45 ---- .../system_module_test/system_module_test.module | 20 ++ .../system_module_test.services.yml | 5 - .../tests/modules/system_test/system_test.module | 13 - 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 ++-- 80 files changed, 1870 insertions(+), 2745 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 5f7a779..5a7cc93 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: @@ -779,7 +783,7 @@ services: 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 77e270b..e2d395f 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 e772289..24d7f42 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -18,8 +18,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; @@ -1646,22 +1644,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, @@ -1669,20 +1669,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')), ); } @@ -1710,35 +1708,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); } /** @@ -1749,8 +1739,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(); @@ -1890,15 +1878,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'; @@ -1918,12 +1897,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()); @@ -2161,7 +2134,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 --- a/core/lib/Drupal/Core/Ajax/AjaxResponseRenderer.php +++ /dev/null @@ -1,93 +0,0 @@ -isOk()) { - return $content; - } - - // Allow controllers to return an HtmlFragment directly. - if ($content instanceof HtmlFragment) { - $content = $content->getContent(); - } - // Most controllers return a render array, but some return a string. - if (!is_array($content)) { - $content = array( - '#markup' => $content, - ); - } - - $response = new AjaxResponse(); - - if (isset($content['#type']) && ($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. - $content += $this->elementInfo('ajax'); - $error = $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->drupalRenderRoot($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->drupalRenderRoot($status_messages); - if (!empty($output)) { - $response->addCommand(new PrependCommand(NULL, $output)); - } - return $response; - } - - /** - * Wraps drupal_render_root(). - * - * @todo: Remove as part of https://drupal.org/node/2182149 - */ - protected function drupalRenderRoot(&$elements) { - $output = drupal_render_root($elements); - drupal_process_attached($elements); - return $output; - } - - /** - * Wraps element_info(). - */ - protected function elementInfo($type) { - return element_info($type); - } - -} 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 88e0efe..0000000 --- a/core/lib/Drupal/Core/Controller/HtmlControllerBase.php +++ /dev/null @@ -1,82 +0,0 @@ -titleResolver = $title_resolver; - $this->renderHtmlRenderer = $render_html_renderer; - } - - /** - * Converts a render array into an HtmlFragment object. - * - * @param array|\Drupal\Core\Page\HtmlFragmentInterface|\Symfony\Component\HttpFoundation\Response $page_content - * The page content area to display. - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. - * - * @return \Drupal\Core\Page\HtmlPage - * A page object. - * - * @throws \InvalidArgumentException - * Thrown if the controller returns a string. - */ - protected function createHtmlFragment($page_content, Request $request) { - // Allow controllers to return a HtmlFragment or a Response object directly. - if ($page_content instanceof HtmlFragment || $page_content instanceof Response) { - return $page_content; - } - - if (is_string($page_content)) { - throw new \InvalidArgumentException('_content controllers are not allowed to return strings. You can return a render array, a html fragment or a response object.'); - } - - $fragment = $this->renderHtmlRenderer->render($page_content); - - if (!$fragment->getTitle() && $route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)) { - $fragment->setTitle($this->titleResolver->getTitle($request, $route), Title::PASS_THROUGH); - } - - return $fragment; - } - -} 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 --- a/core/lib/Drupal/Core/Controller/HtmlPageController.php +++ /dev/null @@ -1,81 +0,0 @@ -controllerResolver = $controller_resolver; - } - - /** - * Controller method for generic HTML pages. - * - * @param Request $request - * The request object. - * @param callable $_content - * The body content callable that contains the body region of this page. - * - * @return \Symfony\Component\HttpFoundation\Response - * A response object. - */ - public function content(Request $request, $_content) { - $page_content = $this->getContentResult($request, $_content); - return $this->createHtmlFragment($page_content, $request); - } - - /** - * 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 array - * The render array that results from invoking the controller. - */ - 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; - } - -} 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/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 --- a/core/lib/Drupal/Core/EventSubscriber/HtmlViewSubscriber.php +++ /dev/null @@ -1,110 +0,0 @@ -fragmentRenderer = $fragment_renderer; - $this->pageRenderer = $page_renderer; - } - - /** - * Converts an HtmlFragment into an HtmlPage. - * - * @param \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event - * The Event to process. - */ - public function onHtmlFragment(GetResponseForControllerResultEvent $event) { - $fragment = $event->getControllerResult(); - if ($fragment instanceof HtmlFragment && !$fragment instanceof HtmlPage) { - $page = $this->fragmentRenderer->render($fragment); - $event->setControllerResult($page); - } - } - - /** - * Renders an HtmlPage object to a Response. - * - * @param \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event - * The Event to process. - */ - public function onHtmlPage(GetResponseForControllerResultEvent $event) { - $page = $event->getControllerResult(); - if ($page instanceof HtmlPage) { - // In case renderPage() returns NULL due to an error cast it to a string - // so as to not cause issues with Response. This also allows renderPage - // to return an object implementing __toString(), but that is not - // recommended. - $response = new Response((string) $this->pageRenderer->render($page), $page->getStatusCode()); - if ($tags = $page->getCacheTags()) { - $response->headers->set('X-Drupal-Cache-Tags', implode(' ', $tags)); - } - if ($keys = $page->getCacheKeys()) { - $response->headers->set('cache_keys', serialize($keys)); - } - if ($max_age = $page->getCacheMaxAge()) { - $response->headers->set('cache_max_age', $max_age); - } - - // Set the generator in the HTTP header. - list($version) = explode('.', \Drupal::VERSION, 2); - $response->headers->set('X-Generator', 'Drupal ' . $version . ' (http://drupal.org)'); - - $event->setResponse($response); - } - } - - /** - * Registers the methods in this class that should be listeners. - * - * @return array - * An array of event listener definitions. - */ - static function getSubscribedEvents() { - $events[KernelEvents::VIEW][] = array('onHtmlFragment', 100); - $events[KernelEvents::VIEW][] = array('onHtmlPage', 50); - - return $events; - } - -} 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 --- a/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php +++ /dev/null @@ -1,155 +0,0 @@ -languageManager = $language_manager; - } - - /** - * {@inheritdoc} - */ - public function render(HtmlFragmentInterface $fragment, $status_code = 200) { - // Converts the given HTML fragment which represents the main content region - // of the page into a render array. - $page_content['main'] = array( - '#markup' => $fragment->getContent(), - ); - $page_content['#title'] = $fragment->getTitle(); - - if ($fragment instanceof CacheableInterface) { - $page_content['main']['#cache']['tags'] = $fragment->getCacheTags(); - } - - // Build the full page array by calling drupal_prepare_page(), which invokes - // hook_page_build(). This adds the other regions to the page. - $page_array = drupal_prepare_page($page_content); - - // Build the HtmlPage object. - $page = new HtmlPage('', array(), $fragment->getTitle()); - $page = $this->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)); - $page->setStatusCode($status_code); - - 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']); - } - - if ($fragment instanceof CacheableInterface) { - // Persist cache tags associated with this page. 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($page_array['page_top']) ? $page_array['page_top']['#cache']['tags'] : [], - $page_array['#cache']['tags'], - isset($page_array['page_bottom']) ? $page_array['page_bottom']['#cache']['tags'] : [], - ['rendered'] - ); - // Only keep unique cache tags. We need to prevent duplicates here already - // rather than only in the cache layer, because they are also used by - // reverse proxies (like Varnish), not only by Drupal's page cache. - $page->setCacheTags(array_unique($cache_tags)); - } - - return $page; - } - - /** - * Enhances a page object based on a render array. - * - * @param \Drupal\Core\Page\HtmlPage $page - * The page object to enhance. - * @param array $page_array - * The page array to extract onto the page object. - * - * @return \Drupal\Core\Page\HtmlPage - * The modified page object. - */ - public function preparePage(HtmlPage $page, &$page_array) { - $page_array['#page'] = $page; - - // HTML element attributes. - $language_interface = $this->languageManager->getCurrentLanguage(); - $html_attributes = $page->getHtmlAttributes(); - $html_attributes['lang'] = $language_interface->getId(); - $html_attributes['dir'] = $language_interface->getDirection(); - - $this->setDefaultMetaTags($page); - - // Add libraries and CSS used by this theme. - $active_theme = \Drupal::theme()->getActiveTheme(); - foreach ($active_theme->getLibraries() as $library) { - $page_array['#attached']['library'][] = $library; - } - foreach ($active_theme->getStyleSheets() as $media => $stylesheets) { - foreach ($stylesheets as $stylesheet) { - $page_array['#attached']['css'][$stylesheet] = array( - 'group' => CSS_AGGREGATE_THEME, - 'every_page' => TRUE, - 'media' => $media - ); - } - } - - return $page; - } - - /** - * Apply the default meta tags to the page object. - * - * @param \Drupal\Core\Page\HtmlPage $page - * The html page. - */ - protected function setDefaultMetaTags(HtmlPage $page) { - // Add default elements. 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. - $page->addMetaElement(new MetaElement(NULL, array( - 'name' => 'charset', - 'charset' => 'utf-8', - ))); - // Show Drupal and the major version number in the META GENERATOR tag. - // Get the major version. - list($version) = explode('.', \Drupal::VERSION, 2); - $page->addMetaElement(new MetaElement('Drupal ' . $version . ' (http://drupal.org)', array( - 'name' => 'Generator', - ))); - - // Display the html.html.twig's default mobile metatags for responsive design. - $page->addMetaElement(new MetaElement(NULL, array('name' => 'viewport', 'content' => 'width=device-width, initial-scale=1.0'))); - } - -} 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 --- a/core/lib/Drupal/Core/Page/DefaultHtmlPageRenderer.php +++ /dev/null @@ -1,125 +0,0 @@ - 'html', - '#page_object' => $page, - ); - // drupal_render() will render the 'html' template, which will call - // HtmlPage::getScripts(). But normally we can only run - // drupal_process_attached() after drupal_render(). Hence any assets - // attached to '#type' => 'html' will be lost. This is a work-around for - // that limitation, until the HtmlPage object contains its assets — this is - // an unfortunate intermediate consequence of the way HtmlPage dictates page - // rendering and how that differs from how drupal_render() works. - $render += element_info($render['#type']); - drupal_process_attached($render); - return drupal_render($render); - } - - /** - * Renders a page using a custom page theme hook and optional region content. - * - * Temporary shim to facilitate modernization progress for special front - * controllers (install.php, update.php, authorize.php), maintenance mode, and - * the exception handler. - * - * Do NOT use this method in your code. This method will be removed as soon - * as architecturally possible. - * - * This is functionally very similar to DefaultHtmlFragmentRenderer::render() - * but with the following important differences: - * - * - drupal_prepare_page() and hook_page_build() cannot be invoked on the - * maintenance and install pages, since possibly enabled page layout/block - * modules would replace the main page content with configured region - * content. - * - This function composes a complete page render array including a page - * template theme suggestion (as opposed to the main page content only). - * - The render cache and cache tags is skipped. - * - * @param array|string $main - * A render array or string containing the main page content. - * @param string $title - * (optional) The page title. - * @param string $theme - * (optional) The theme hook to use for rendering the page. Defaults to - * 'maintenance'. The given value will be appended with '_page' to compose - * the #theme property for #type 'page' currently; e.g., 'maintenance' - * becomes 'maintenance_page'. Ultimately this parameter will be converted - * into a page template theme suggestion; i.e., 'page__$theme'. - * @param array $regions - * (optional) Additional region content to add to the page. The given array - * is added to the page render array, so this parameter may also be used to - * pass e.g. the #show_messages property for #type 'page'. - * - * @return string - * The rendered HTML page. - * - * @internal - */ - public static function renderPage($main, $title = '', $theme = 'maintenance', array $regions = array()) { - // Automatically convert the main page content into a render array. - if (!is_array($main)) { - $main = array('#markup' => $main); - } - $page = new HtmlPage('', array(), $title); - $page_array = array( - '#type' => 'page', - // @todo Change into theme suggestions "page__$theme". - '#theme' => $theme . '_page', - '#title' => $title, - 'content' => array( - 'system_main' => $main, - ), - ); - // Append region content. - $page_array += $regions; - // Add default properties. - $page_array += element_info('page'); - - // hook_page_build() cannot be invoked on the maintenance and install pages, - // because the application is in an unknown or special state. - // In particular on the install page, invoking hook_page_build() directly - // after e.g. Block module has been installed would *replace* the installer - // output with the configured blocks of the installer theme (loaded from - // default configuration of the installation profile). - - // Allow modules and themes to alter the page render array. - // This allows e.g. themes to attach custom libraries. - \Drupal::moduleHandler()->alter('page', $page_array); - - // @todo Move preparePage() before alter() above, so $page_array['#page'] is - // available in hook_page_alter(), so that HTML attributes can be altered. - $page = \Drupal::service('html_fragment_renderer')->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']); - } - - return \Drupal::service('html_page_renderer')->render($page); - } - -} 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 --- a/core/lib/Drupal/Core/Page/FeedLinkElement.php +++ /dev/null @@ -1,32 +0,0 @@ -. - * - * @var bool - */ - protected $noScript = FALSE; - - /** - * Renders this object to an HTML element string. - * - * @return string - */ - public function __toString() { - // Render the attributes via the attribute template class. - // @todo Should HeadElement just extend the Attribute classes? - $attributes = new Attribute($this->attributes); - $rendered = (string) $attributes; - - $string = "<{$this->element}{$rendered} />"; - if ($this->noScript) { - $string = ""; - } - return SafeMarkup::set($string); - } - - /** - * Sets an attribute on this element. - * - * @param mixed $key - * The attribute to set. - * @param mixed $value - * The value to which to set it. - * - * @return $this - */ - public function setAttribute($key, $value) { - $this->attributes[$key] = $value; - return $this; - } - - /** - * Gets all the attributes. - * - * @return array - * An array of all the attributes keyed by name of attribute. - */ - public function &getAttributes() { - return $this->attributes; - } - - /** - * Sets if this element should be wrapped in