Issue #2476947 by mdrummond, Wim Leers, davidhernandez, lauriii, joelpittet, andypost, Cottser, JeroenT, Manuel Garcia, rpayanm: Convert "title" page element into a block

8.0.x
Nathaniel Catchpole 2015-09-30 14:00:49 +01:00
parent f8e3bf2352
commit bef7274a7c
83 changed files with 862 additions and 162 deletions

View File

@ -1342,9 +1342,6 @@ function template_preprocess_html(&$variables) {
function template_preprocess_page(&$variables) { function template_preprocess_page(&$variables) {
$language_interface = \Drupal::languageManager()->getCurrentLanguage(); $language_interface = \Drupal::languageManager()->getCurrentLanguage();
// Move some variables to the top level for themer convenience and template cleanliness.
$variables['title'] = $variables['page']['#title'];
foreach (\Drupal::theme()->getActiveTheme()->getRegions() as $region) { foreach (\Drupal::theme()->getActiveTheme()->getRegions() as $region) {
if (!isset($variables['page'][$region])) { if (!isset($variables['page'][$region])) {
$variables['page'][$region] = array(); $variables['page'][$region] = array();
@ -1465,6 +1462,10 @@ function template_preprocess_maintenance_page(&$variables) {
$variables['logo'] = theme_get_setting('logo.url'); $variables['logo'] = theme_get_setting('logo.url');
$variables['site_name'] = $site_config->get('name'); $variables['site_name'] = $site_config->get('name');
$variables['site_slogan'] = $site_config->get('slogan'); $variables['site_slogan'] = $site_config->get('slogan');
// Maintenance page and install page need page title in variable because there
// are no blocks.
$variables['title'] = $variables['page']['#title'];
} }
/** /**
@ -1707,6 +1708,9 @@ function drupal_common_theme() {
'page' => array( 'page' => array(
'render element' => 'page', 'render element' => 'page',
), ),
'page_title' => array(
'variables' => array('title' => NULL),
),
'region' => array( 'region' => array(
'render element' => 'elements', 'render element' => 'elements',
), ),

View File

@ -10,7 +10,7 @@ namespace Drupal\Core\Block;
/** /**
* The interface for "main page content" blocks. * The interface for "main page content" blocks.
* *
* A main page content block represents the content returns by the controller. * A main page content block represents the content returned by the controller.
* *
* @ingroup block_api * @ingroup block_api
*/ */

View File

@ -0,0 +1,55 @@
<?php
/**
* @file
* Contains \Drupal\Core\Plugin\Block\PageTitleBlock.
*/
namespace Drupal\Core\Block\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Block\TitleBlockPluginInterface;
/**
* Provides a block to display the page title.
*
* @Block(
* id = "page_title_block",
* admin_label = @Translation("Page title"),
* )
*/
class PageTitleBlock extends BlockBase implements TitleBlockPluginInterface {
/**
* The page title: a string (plain title) or a render array (formatted title).
*
* @var string|array
*/
protected $title = '';
/**
* {@inheritdoc}
*/
public function setTitle($title) {
$this->title = $title;
return $this;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return ['label_display' => FALSE];
}
/**
* {@inheritdoc}
*/
public function build() {
return [
'#type' => 'page_title',
'#title' => $this->title,
];
}
}

View File

@ -0,0 +1,30 @@
<?php
/**
* @file
* Contains \Drupal\Core\Block\TitleBlockPluginInterface.
*/
namespace Drupal\Core\Block;
/**
* The interface for "title" blocks.
*
* A title block shows the title returned by the controller.
*
* @ingroup block_api
*
* @see \Drupal\Core\Render\Element\PageTitle
*/
interface TitleBlockPluginInterface extends BlockPluginInterface {
/**
* Sets the title.
*
* @param string|array $title
* The page title: either a string for plain titles or a render array for
* formatted titles.
*/
public function setTitle($title);
}

View File

@ -36,4 +36,15 @@ interface PageVariantInterface extends VariantInterface {
*/ */
public function setMainContent(array $main_content); public function setMainContent(array $main_content);
/**
* Sets the title for the page being rendered.
*
* @param string|array $title
* The page title: either a string for plain titles or a render array for
* formatted titles.
*
* @return $this
*/
public function setTitle($title);
} }

View File

@ -0,0 +1,31 @@
<?php
/**
* @file
* Contains \Drupal\Core\Render\Element\PageTitle.
*/
namespace Drupal\Core\Render\Element;
/**
* Provides a render element for the title of an HTML page.
*
* This represents the title of the HTML page's body.
*
* @RenderElement("page_title")
*/
class PageTitle extends RenderElement {
/**
* {@inheritdoc}
*/
public function getInfo() {
return [
'#theme' => 'page_title',
// The page title: either a string for plain titles or a render array for
// formatted titles.
'#title' => NULL,
];
}
}

View File

@ -193,11 +193,18 @@ class HtmlRenderer implements MainContentRendererInterface {
* If the selected display variant does not implement PageVariantInterface. * If the selected display variant does not implement PageVariantInterface.
*/ */
protected function prepare(array $main_content, Request $request, RouteMatchInterface $route_match) { protected function prepare(array $main_content, Request $request, RouteMatchInterface $route_match) {
// Determine the title: use the title provided by the main content if any,
// otherwise get it from the routing information.
$get_title = function (array $main_content) use ($request, $route_match) {
return isset($main_content['#title']) ? $main_content['#title'] : $this->titleResolver->getTitle($request, $route_match->getRouteObject());
};
// If the _controller result already is #type => page, // If the _controller result already is #type => page,
// we have no work to do: The "main content" already is an entire "page" // we have no work to do: The "main content" already is an entire "page"
// (see html.html.twig). // (see html.html.twig).
if (isset($main_content['#type']) && $main_content['#type'] === 'page') { if (isset($main_content['#type']) && $main_content['#type'] === 'page') {
$page = $main_content; $page = $main_content;
$title = $get_title($page);
} }
// Otherwise, render it as the main content of a #type => page, by selecting // 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. // page display variant to do that and building that page display variant.
@ -229,6 +236,8 @@ class HtmlRenderer implements MainContentRendererInterface {
]; ];
} }
$title = $get_title($main_content);
// Instantiate the page display, and give it the main content. // Instantiate the page display, and give it the main content.
$page_display = $this->displayVariantManager->createInstance($variant_id); $page_display = $this->displayVariantManager->createInstance($variant_id);
if (!$page_display instanceof PageVariantInterface) { if (!$page_display instanceof PageVariantInterface) {
@ -236,6 +245,7 @@ class HtmlRenderer implements MainContentRendererInterface {
} }
$page_display $page_display
->setMainContent($main_content) ->setMainContent($main_content)
->setTitle($title)
->addCacheableDependency($event) ->addCacheableDependency($event)
->setConfiguration($event->getPluginConfiguration()); ->setConfiguration($event->getPluginConfiguration());
// Some display variants need to be passed an array of contexts with // Some display variants need to be passed an array of contexts with
@ -268,10 +278,6 @@ class HtmlRenderer implements MainContentRendererInterface {
// Allow hooks to add attachments to $page['#attached']. // Allow hooks to add attachments to $page['#attached'].
$this->invokePageAttachmentHooks($page); $this->invokePageAttachmentHooks($page);
// Determine the title: use the title provided by the main content if any,
// otherwise get it from the routing information.
$title = isset($main_content['#title']) ? $main_content['#title'] : $this->titleResolver->getTitle($request, $route_match->getRouteObject());
return [$page, $title]; return [$page, $title];
} }

View File

@ -27,6 +27,13 @@ class SimplePageVariant extends VariantBase implements PageVariantInterface {
*/ */
protected $mainContent; protected $mainContent;
/**
* The page title: a string (plain title) or a render array (formatted title).
*
* @var string|array
*/
protected $title = '';
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -35,17 +42,30 @@ class SimplePageVariant extends VariantBase implements PageVariantInterface {
return $this; return $this;
} }
/**
* {@inheritdoc}
*/
public function setTitle($title) {
$this->title = $title;
return $this;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function build() { public function build() {
$build = [ $build = [
'content' => [ 'content' => [
'main_content' => $this->mainContent,
'messages' => [ 'messages' => [
'#type' => 'status_messages', '#type' => 'status_messages',
'#weight' => -1000, '#weight' => -1000,
], ],
'page_title' => [
'#type' => 'page_title',
'#title' => $this->title,
'#weight' => -900,
],
'main_content' => ['#weight' => -800] + $this->mainContent,
], ],
]; ];
return $build; return $build;

View File

@ -14,6 +14,13 @@ use Drupal\Component\Utility\Html;
* @group aggregator * @group aggregator
*/ */
class AddFeedTest extends AggregatorTestBase { class AddFeedTest extends AggregatorTestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/** /**
* Creates and ensures that a feed is unique, checks source, and deletes feed. * Creates and ensures that a feed is unique, checks source, and deletes feed.
*/ */

View File

@ -23,6 +23,12 @@ class AggregatorRenderingTest extends AggregatorTestBase {
*/ */
public static $modules = array('block', 'test_page_test'); public static $modules = array('block', 'test_page_test');
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/** /**
* Adds a feed block to the page and checks its links. * Adds a feed block to the page and checks its links.
*/ */

View File

@ -8,6 +8,7 @@
namespace Drupal\block; namespace Drupal\block;
use Drupal\Core\Block\MainContentBlockPluginInterface; use Drupal\Core\Block\MainContentBlockPluginInterface;
use Drupal\Core\Block\TitleBlockPluginInterface;
use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityManagerInterface;
@ -100,12 +101,13 @@ class BlockViewBuilder extends EntityViewBuilder {
'tags' => $cache_tags, 'tags' => $cache_tags,
'max-age' => $plugin->getCacheMaxAge(), 'max-age' => $plugin->getCacheMaxAge(),
], ],
'#weight' => $entity->getWeight(),
); );
// Allow altering of cacheability metadata or setting #create_placeholder. // Allow altering of cacheability metadata or setting #create_placeholder.
$this->moduleHandler->alter(['block_build', "block_build_" . $plugin->getBaseId()], $build[$entity_id], $plugin); $this->moduleHandler->alter(['block_build', "block_build_" . $plugin->getBaseId()], $build[$entity_id], $plugin);
if ($plugin instanceof MainContentBlockPluginInterface) { if ($plugin instanceof MainContentBlockPluginInterface || $plugin instanceof TitleBlockPluginInterface) {
// Immediately build a #pre_render-able block, since this block cannot // Immediately build a #pre_render-able block, since this block cannot
// be built lazily. // be built lazily.
$build[$entity_id] += static::buildPreRenderableBlock($entity, $this->moduleHandler()); $build[$entity_id] += static::buildPreRenderableBlock($entity, $this->moduleHandler());

View File

@ -9,6 +9,7 @@ namespace Drupal\block\Plugin\DisplayVariant;
use Drupal\block\BlockRepositoryInterface; use Drupal\block\BlockRepositoryInterface;
use Drupal\Core\Block\MainContentBlockPluginInterface; use Drupal\Core\Block\MainContentBlockPluginInterface;
use Drupal\Core\Block\TitleBlockPluginInterface;
use Drupal\Core\Block\MessagesBlockPluginInterface; use Drupal\Core\Block\MessagesBlockPluginInterface;
use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Display\PageVariantInterface; use Drupal\Core\Display\PageVariantInterface;
@ -63,6 +64,13 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont
*/ */
protected $mainContent = []; protected $mainContent = [];
/**
* The page title: a string (plain title) or a render array (formatted title).
*
* @var string|array
*/
protected $title = '';
/** /**
* Constructs a new BlockPageVariant. * Constructs a new BlockPageVariant.
* *
@ -108,6 +116,14 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont
return $this; return $this;
} }
/**
* {@inheritdoc}
*/
public function setTitle($title) {
$this->title = $title;
return $this;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -131,6 +147,9 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont
$block_plugin->setMainContent($this->mainContent); $block_plugin->setMainContent($this->mainContent);
$main_content_block_displayed = TRUE; $main_content_block_displayed = TRUE;
} }
elseif ($block_plugin instanceof TitleBlockPluginInterface) {
$block_plugin->setTitle($this->title);
}
elseif ($block_plugin instanceof MessagesBlockPluginInterface) { elseif ($block_plugin instanceof MessagesBlockPluginInterface) {
$messages_block_displayed = TRUE; $messages_block_displayed = TRUE;
} }
@ -138,8 +157,9 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont
// The main content block cannot be cached: it is a placeholder for the // The main content block cannot be cached: it is a placeholder for the
// render array returned by the controller. It should be rendered as-is, // render array returned by the controller. It should be rendered as-is,
// with other placed blocks "decorating" it. // with other placed blocks "decorating" it. Analogous reasoning for the
if ($block_plugin instanceof MainContentBlockPluginInterface) { // title block.
if ($block_plugin instanceof MainContentBlockPluginInterface || $block_plugin instanceof TitleBlockPluginInterface) {
unset($build[$region][$key]['#cache']['keys']); unset($build[$region][$key]['#cache']['keys']);
} }
} }
@ -165,6 +185,12 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont
]; ];
} }
// If any render arrays are manually placed, render arrays and blocks must
// be sorted.
if (!$main_content_block_displayed || !$messages_block_displayed) {
unset($build['content']['#sorted']);
}
// The access results' cacheability is currently added to the top level of the // The access results' cacheability is currently added to the top level of the
// render array. This is done to prevent issues with empty regions being // render array. This is done to prevent issues with empty regions being
// displayed. // displayed.

View File

@ -135,6 +135,9 @@ class BlockTest extends BlockTestBase {
* Test configuring and moving a module-define block to specific regions. * Test configuring and moving a module-define block to specific regions.
*/ */
function testBlock() { function testBlock() {
// Place page title block to test error messages.
$this->drupalPlaceBlock('page_title_block');
// Select the 'Powered by Drupal' block to be configured and moved. // Select the 'Powered by Drupal' block to be configured and moved.
$block = array(); $block = array();
$block['id'] = 'system_powered_by_block'; $block['id'] = 'system_powered_by_block';

View File

@ -74,28 +74,32 @@ class BlockPageVariantTest extends UnitTestCase {
public function providerBuild() { public function providerBuild() {
$blocks_config = array( $blocks_config = array(
'block1' => array( 'block1' => array(
// region, is main content block, is messages block // region, is main content block, is messages block, is title block
'top', FALSE, FALSE, 'top', FALSE, FALSE, FALSE,
), ),
// Test multiple blocks in the same region. // Test multiple blocks in the same region.
'block2' => array( 'block2' => array(
'bottom', FALSE, FALSE, 'bottom', FALSE, FALSE, FALSE,
), ),
'block3' => array( 'block3' => array(
'bottom', FALSE, FALSE, 'bottom', FALSE, FALSE, FALSE,
), ),
// Test a block implementing MainContentBlockPluginInterface. // Test a block implementing MainContentBlockPluginInterface.
'block4' => array( 'block4' => array(
'center', TRUE, FALSE, 'center', TRUE, FALSE, FALSE,
), ),
// Test a block implementing MessagesBlockPluginInterface. // Test a block implementing MessagesBlockPluginInterface.
'block5' => array( 'block5' => array(
'center', FALSE, TRUE, 'center', FALSE, TRUE, FALSE,
),
// Test a block implementing TitleBlockPluginInterface.
'block6' => array(
'center', FALSE, FALSE, TRUE,
), ),
); );
$test_cases = []; $test_cases = [];
$test_cases[] = [$blocks_config, 5, $test_cases[] = [$blocks_config, 6,
[ [
'#cache' => [ '#cache' => [
'tags' => [ 'tags' => [
@ -113,6 +117,7 @@ class BlockPageVariantTest extends UnitTestCase {
'center' => [ 'center' => [
'block4' => [], 'block4' => [],
'block5' => [], 'block5' => [],
'block6' => [],
'#sorted' => TRUE, '#sorted' => TRUE,
], ],
'bottom' => [ 'bottom' => [
@ -123,7 +128,7 @@ class BlockPageVariantTest extends UnitTestCase {
], ],
]; ];
unset($blocks_config['block5']); unset($blocks_config['block5']);
$test_cases[] = [$blocks_config, 4, $test_cases[] = [$blocks_config, 5,
[ [
'#cache' => [ '#cache' => [
'tags' => [ 'tags' => [
@ -139,6 +144,7 @@ class BlockPageVariantTest extends UnitTestCase {
], ],
'center' => [ 'center' => [
'block4' => [], 'block4' => [],
'block6' => [],
'#sorted' => TRUE, '#sorted' => TRUE,
], ],
'bottom' => [ 'bottom' => [
@ -157,6 +163,7 @@ class BlockPageVariantTest extends UnitTestCase {
], ],
]; ];
unset($blocks_config['block4']); unset($blocks_config['block4']);
unset($blocks_config['block6']);
$test_cases[] = [$blocks_config, 3, $test_cases[] = [$blocks_config, 3,
[ [
'#cache' => [ '#cache' => [
@ -205,6 +212,7 @@ class BlockPageVariantTest extends UnitTestCase {
$block_plugin = $this->getMock('Drupal\Core\Block\BlockPluginInterface'); $block_plugin = $this->getMock('Drupal\Core\Block\BlockPluginInterface');
$main_content_block_plugin = $this->getMock('Drupal\Core\Block\MainContentBlockPluginInterface'); $main_content_block_plugin = $this->getMock('Drupal\Core\Block\MainContentBlockPluginInterface');
$messages_block_plugin = $this->getMock('Drupal\Core\Block\MessagesBlockPluginInterface'); $messages_block_plugin = $this->getMock('Drupal\Core\Block\MessagesBlockPluginInterface');
$title_block_plugin = $this->getMock('Drupal\Core\Block\TitleBlockPluginInterface');
foreach ($blocks_config as $block_id => $block_config) { foreach ($blocks_config as $block_id => $block_config) {
$block = $this->getMock('Drupal\block\BlockInterface'); $block = $this->getMock('Drupal\block\BlockInterface');
$block->expects($this->any()) $block->expects($this->any())
@ -212,7 +220,7 @@ class BlockPageVariantTest extends UnitTestCase {
->willReturn([]); ->willReturn([]);
$block->expects($this->atLeastOnce()) $block->expects($this->atLeastOnce())
->method('getPlugin') ->method('getPlugin')
->willReturn($block_config[1] ? $main_content_block_plugin : ($block_config[2] ? $messages_block_plugin : $block_plugin)); ->willReturn($block_config[1] ? $main_content_block_plugin : ($block_config[2] ? $messages_block_plugin : ($block_config[3] ? $title_block_plugin : $block_plugin)));
$blocks[$block_config[0]][$block_id] = $block; $blocks[$block_config[0]][$block_id] = $block;
} }
$this->blockViewBuilder->expects($this->exactly($visible_block_count)) $this->blockViewBuilder->expects($this->exactly($visible_block_count))

View File

@ -50,6 +50,8 @@ class BlockContentTranslationUITest extends ContentTranslationUITestBase {
$this->bundle = 'basic'; $this->bundle = 'basic';
$this->testLanguageSelector = FALSE; $this->testLanguageSelector = FALSE;
parent::setUp(); parent::setUp();
$this->drupalPlaceBlock('page_title_block');
} }
/** /**

View File

@ -42,6 +42,12 @@ class BlockContentTypeTest extends BlockContentTestBase {
*/ */
protected $autoCreateBasicBlockType = FALSE; protected $autoCreateBasicBlockType = FALSE;
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/** /**
* Tests creating a block type programmatically and via a form. * Tests creating a block type programmatically and via a form.
*/ */

View File

@ -17,6 +17,12 @@ use Drupal\Component\Utility\Unicode;
*/ */
class PageEditTest extends BlockContentTestBase { class PageEditTest extends BlockContentTestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/** /**
* Checks block edit functionality. * Checks block edit functionality.
*/ */

View File

@ -68,6 +68,7 @@ class BookTest extends WebTestBase {
protected function setUp() { protected function setUp() {
parent::setUp(); parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block'); $this->drupalPlaceBlock('system_breadcrumb_block');
$this->drupalPlaceBlock('page_title_block');
// node_access_test requires a node_access_rebuild(). // node_access_test requires a node_access_rebuild().
node_access_rebuild(); node_access_rebuild();

View File

@ -15,6 +15,13 @@ use Drupal\user\RoleInterface;
* @group comment * @group comment
*/ */
class CommentAdminTest extends CommentTestBase { class CommentAdminTest extends CommentTestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/** /**
* Test comment approval functionality through admin/content/comment. * Test comment approval functionality through admin/content/comment.
*/ */

View File

@ -50,6 +50,7 @@ class CommentNonNodeTest extends WebTestBase {
protected function setUp() { protected function setUp() {
parent::setUp(); parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block'); $this->drupalPlaceBlock('system_breadcrumb_block');
$this->drupalPlaceBlock('page_title_block');
// Create a bundle for entity_test. // Create a bundle for entity_test.
entity_test_create_bundle('entity_test', 'Entity Test', 'entity_test'); entity_test_create_bundle('entity_test', 'Entity Test', 'entity_test');

View File

@ -191,14 +191,22 @@ abstract class CommentTestBase extends WebTestBase {
*/ */
function commentExists(CommentInterface $comment = NULL, $reply = FALSE) { function commentExists(CommentInterface $comment = NULL, $reply = FALSE) {
if ($comment) { if ($comment) {
$regex = '!' . ($reply ? '<div class="indented">(.*?)' : ''); $comment_element = $this->cssSelect('.comment-wrapper ' . ($reply ? '.indented ' : '') . '#comment-' . $comment->id() . ' ~ article');
$regex .= '<a id="comment-' . $comment->id() . '"(.*?)'; if (empty($comment_element)) {
$regex .= $comment->getSubject() . '(.*?)'; return FALSE;
$regex .= $comment->comment_body->value . '(.*?)'; }
$regex .= ($reply ? '</article>\s</div>(.*?)' : '');
$regex .= '!s';
return (boolean) preg_match($regex, $this->getRawContent()); $comment_title = $comment_element[0]->xpath('div/h3/a');
if (empty($comment_title) || ((string)$comment_title[0]) !== $comment->getSubject()) {
return FALSE;
}
$comment_body = $comment_element[0]->xpath('div/div/p');
if (empty($comment_body) || ((string)$comment_body[0]) !== $comment->comment_body->value) {
return FALSE;
}
return TRUE;
} }
else { else {
return FALSE; return FALSE;

View File

@ -43,6 +43,9 @@ class CommentTypeTest extends CommentTestBase {
*/ */
protected function setUp() { protected function setUp() {
parent::setUp(); parent::setUp();
$this->drupalPlaceBlock('page_title_block');
$this->adminUser = $this->drupalCreateUser($this->permissions); $this->adminUser = $this->drupalCreateUser($this->permissions);
} }

View File

@ -28,6 +28,12 @@ class ConfigSingleImportExportTest extends WebTestBase {
'config_test' 'config_test'
]; ];
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/** /**
* Tests importing a single configuration file. * Tests importing a single configuration file.
*/ */

View File

@ -69,6 +69,7 @@ class ConfigTranslationOverviewTest extends WebTestBase {
} }
$this->localeStorage = $this->container->get('locale.storage'); $this->localeStorage = $this->container->get('locale.storage');
$this->drupalPlaceBlock('local_tasks_block'); $this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
} }
/** /**

View File

@ -119,6 +119,7 @@ class ConfigTranslationUiTest extends WebTestBase {
} }
$this->localeStorage = $this->container->get('locale.storage'); $this->localeStorage = $this->container->get('locale.storage');
$this->drupalPlaceBlock('local_tasks_block'); $this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
} }
/** /**

View File

@ -40,6 +40,7 @@ class ContactSitewideTest extends WebTestBase {
parent::setUp(); parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block'); $this->drupalPlaceBlock('system_breadcrumb_block');
$this->drupalPlaceBlock('local_actions_block'); $this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('page_title_block');
} }
/** /**

View File

@ -49,6 +49,7 @@ class DbLogTest extends WebTestBase {
protected function setUp() { protected function setUp() {
parent::setUp(); parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block'); $this->drupalPlaceBlock('system_breadcrumb_block');
$this->drupalPlaceBlock('page_title_block');
// Create users with specific permissions. // Create users with specific permissions.
$this->adminUser = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports', 'administer users')); $this->adminUser = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports', 'administer users'));

View File

@ -30,6 +30,7 @@ class EntityDisplayModeTest extends WebTestBase {
parent::setUp(); parent::setUp();
$this->drupalPlaceBlock('local_actions_block'); $this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('page_title_block');
} }
/** /**

View File

@ -43,6 +43,7 @@ class FieldUIDeleteTest extends WebTestBase {
$this->drupalPlaceBlock('system_breadcrumb_block'); $this->drupalPlaceBlock('system_breadcrumb_block');
$this->drupalPlaceBlock('local_tasks_block'); $this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
// Create a test user. // Create a test user.
$admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer node fields', 'administer node form display', 'administer node display', 'administer users', 'administer account settings', 'administer user display', 'bypass node access')); $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer node fields', 'administer node form display', 'administer node display', 'administer users', 'administer account settings', 'administer user display', 'bypass node access'));

View File

@ -70,6 +70,7 @@ class ManageFieldsTest extends WebTestBase {
$this->drupalPlaceBlock('system_breadcrumb_block'); $this->drupalPlaceBlock('system_breadcrumb_block');
$this->drupalPlaceBlock('local_actions_block'); $this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('local_tasks_block'); $this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
// Create a test user. // Create a test user.
$admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer node fields', 'administer node form display', 'administer node display', 'administer taxonomy', 'administer taxonomy_term fields', 'administer taxonomy_term display', 'administer users', 'administer account settings', 'administer user display', 'bypass node access')); $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer node fields', 'administer node form display', 'administer node display', 'administer taxonomy', 'administer taxonomy_term fields', 'administer taxonomy_term display', 'administer users', 'administer account settings', 'administer user display', 'bypass node access'));

View File

@ -71,6 +71,8 @@ class FilterFormatAccessTest extends WebTestBase {
protected function setUp() { protected function setUp() {
parent::setUp(); parent::setUp();
$this->drupalPlaceBlock('page_title_block');
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
// Create a user who can administer text formats, but does not have // Create a user who can administer text formats, but does not have

View File

@ -83,6 +83,7 @@ class ForumTest extends WebTestBase {
protected function setUp() { protected function setUp() {
parent::setUp(); parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block'); $this->drupalPlaceBlock('system_breadcrumb_block');
$this->drupalPlaceBlock('page_title_block');
// Create users. // Create users.
$this->adminUser = $this->drupalCreateUser(array( $this->adminUser = $this->drupalCreateUser(array(

View File

@ -35,6 +35,7 @@ class MenuNodeTest extends WebTestBase {
parent::setUp(); parent::setUp();
$this->drupalPlaceBlock('system_menu_block:main'); $this->drupalPlaceBlock('system_menu_block:main');
$this->drupalPlaceBlock('page_title_block');
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));

View File

@ -70,6 +70,8 @@ class MenuTest extends MenuWebTestBase {
protected function setUp() { protected function setUp() {
parent::setUp(); parent::setUp();
$this->drupalPlaceBlock('page_title_block');
$this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
// Create users. // Create users.

View File

@ -100,6 +100,7 @@ class PageCacheTagsIntegrationTest extends WebTestBase {
'config:block.block.bartik_messages', 'config:block.block.bartik_messages',
'config:block.block.bartik_local_actions', 'config:block.block.bartik_local_actions',
'config:block.block.bartik_local_tasks', 'config:block.block.bartik_local_tasks',
'config:block.block.bartik_page_title',
'node_view', 'node_view',
'node:' . $node_1->id(), 'node:' . $node_1->id(),
'user:0', 'user:0',
@ -138,6 +139,7 @@ class PageCacheTagsIntegrationTest extends WebTestBase {
'config:block.block.bartik_messages', 'config:block.block.bartik_messages',
'config:block.block.bartik_local_actions', 'config:block.block.bartik_local_actions',
'config:block.block.bartik_local_tasks', 'config:block.block.bartik_local_tasks',
'config:block.block.bartik_page_title',
'node_view', 'node_view',
'node:' . $node_2->id(), 'node:' . $node_2->id(),
'user:' . $author_2->id(), 'user:' . $author_2->id(),

View File

@ -59,6 +59,7 @@ class SearchConfigSettingsFormTest extends SearchTestBase {
// Enable the search block. // Enable the search block.
$this->drupalPlaceBlock('search_form_block'); $this->drupalPlaceBlock('search_form_block');
$this->drupalPlaceBlock('local_tasks_block'); $this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
} }
/** /**

View File

@ -39,6 +39,7 @@ class SearchPageTextTest extends SearchTestBase {
// Create user. // Create user.
$this->searchingUser = $this->drupalCreateUser(array('search content', 'access user profiles', 'use advanced search')); $this->searchingUser = $this->drupalCreateUser(array('search content', 'access user profiles', 'use advanced search'));
$this->drupalPlaceBlock('local_tasks_block'); $this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
} }
/** /**

View File

@ -298,9 +298,9 @@ function shortcut_preprocess_block(&$variables) {
} }
/** /**
* Implements hook_preprocess_HOOK() for page templates. * Implements hook_preprocess_HOOK() for page title templates.
*/ */
function shortcut_preprocess_page(&$variables) { function shortcut_preprocess_page_title(&$variables) {
// Only display the shortcut link if the user has the ability to edit // Only display the shortcut link if the user has the ability to edit
// shortcuts and if the page's actual content is being shown (for example, // shortcuts and if the page's actual content is being shown (for example,
// we do not want to display it on "access denied" or "page not found" // we do not want to display it on "access denied" or "page not found"
@ -309,9 +309,12 @@ function shortcut_preprocess_page(&$variables) {
$link = Url::fromRouteMatch(\Drupal::routeMatch())->getInternalPath(); $link = Url::fromRouteMatch(\Drupal::routeMatch())->getInternalPath();
$route_match = \Drupal::routeMatch(); $route_match = \Drupal::routeMatch();
// Replicate template_preprocess_html()'s processing to get the title in
// string form, so we can set the default name for the shortcut.
$name = render($variables['title']);
$query = array( $query = array(
'link' => $link, 'link' => $link,
'name' => $variables['title'], 'name' => $name,
); );
$shortcut_set = shortcut_current_displayed_set(); $shortcut_set = shortcut_current_displayed_set();

View File

@ -26,6 +26,15 @@ class ShortcutLinksTest extends ShortcutTestBase {
*/ */
public static $modules = array('router_test', 'views', 'block'); public static $modules = array('router_test', 'views', 'block');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/** /**
* Tests that creating a shortcut works properly. * Tests that creating a shortcut works properly.
*/ */

View File

@ -47,7 +47,7 @@ class EntityViewControllerTest extends WebTestBase {
*/ */
function testEntityViewController() { function testEntityViewController() {
$get_label_markup = function($label) { $get_label_markup = function($label) {
return '<h1> return '<h1 class="page-title">
<div class="field field--name-name field--type-string field--label-hidden field__item">' . $label . '</div> <div class="field field--name-name field--type-string field--label-hidden field__item">' . $label . '</div>
</h1>'; </h1>';
}; };

View File

@ -40,6 +40,41 @@ class InstallerTest extends InstallerTestBase {
// metatags as expected to the first page of the installer. // metatags as expected to the first page of the installer.
$this->assertRaw('core/themes/seven/css/components/buttons.css'); $this->assertRaw('core/themes/seven/css/components/buttons.css');
$this->assertRaw('<meta charset="utf-8" />'); $this->assertRaw('<meta charset="utf-8" />');
// Assert that the expected title is present.
$this->assertEqual('Choose language', $this->cssSelect('main h1')[0]);
parent::setUpLanguage(); parent::setUpLanguage();
} }
/**
* {@inheritdoc}
*/
protected function setUpProfile() {
// Assert that the expected title is present.
$this->assertEqual('Select an installation profile', $this->cssSelect('main h1')[0]);
parent::setUpProfile();
}
/**
* {@inheritdoc}
*/
protected function setUpSettings() {
// Assert that the expected title is present.
$this->assertEqual('Database configuration', $this->cssSelect('main h1')[0]);
parent::setUpSettings();
}
/**
* {@inheritdoc}
*/
protected function setUpSite() {
// Assert that the expected title is present.
$this->assertEqual('Configure site', $this->cssSelect('main h1')[0]);
parent::setUpSite();
}
} }

View File

@ -44,6 +44,7 @@ class MenuRouterTest extends WebTestBase {
$this->drupalPlaceBlock('system_menu_block:tools'); $this->drupalPlaceBlock('system_menu_block:tools');
$this->drupalPlaceBlock('local_tasks_block'); $this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
} }
/** /**

View File

@ -30,6 +30,8 @@ class AccessDeniedTest extends WebTestBase {
protected function setUp() { protected function setUp() {
parent::setUp(); parent::setUp();
$this->drupalPlaceBlock('page_title_block');
// Create an administrative user. // Create an administrative user.
$this->adminUser = $this->drupalCreateUser(['access administration pages', 'administer site configuration', 'link to any page', 'administer blocks']); $this->adminUser = $this->drupalCreateUser(['access administration pages', 'administer site configuration', 'link to any page', 'administer blocks']);

View File

@ -36,6 +36,8 @@ class PageTitleTest extends WebTestBase {
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
$this->drupalPlaceBlock('page_title_block');
$this->contentUser = $this->drupalCreateUser(array('create page content', 'access content', 'administer themes', 'administer site configuration', 'link to any page')); $this->contentUser = $this->drupalCreateUser(array('create page content', 'access content', 'administer themes', 'administer site configuration', 'link to any page'));
$this->drupalLogin($this->contentUser); $this->drupalLogin($this->contentUser);
} }
@ -105,14 +107,14 @@ class PageTitleTest extends WebTestBase {
$this->drupalGet('test-render-title'); $this->drupalGet('test-render-title');
$this->assertTitle('Foo | Drupal'); $this->assertTitle('Foo | Drupal');
$result = $this->xpath('//h1'); $result = $this->xpath('//h1[@class="page-title"]');
$this->assertEqual('Foo', (string) $result[0]); $this->assertEqual('Foo', (string) $result[0]);
// Test forms // Test forms
$this->drupalGet('form-test/object-builder'); $this->drupalGet('form-test/object-builder');
$this->assertTitle('Test dynamic title | Drupal'); $this->assertTitle('Test dynamic title | Drupal');
$result = $this->xpath('//h1'); $result = $this->xpath('//h1[@class="page-title"]');
$this->assertEqual('Test dynamic title', (string) $result[0]); $this->assertEqual('Test dynamic title', (string) $result[0]);
// Set some custom translated strings. // Set some custom translated strings.
@ -125,14 +127,14 @@ class PageTitleTest extends WebTestBase {
$this->drupalGet('test-page-static-title'); $this->drupalGet('test-page-static-title');
$this->assertTitle('Static title translated | Drupal'); $this->assertTitle('Static title translated | Drupal');
$result = $this->xpath('//h1'); $result = $this->xpath('//h1[@class="page-title"]');
$this->assertEqual('Static title translated', (string) $result[0]); $this->assertEqual('Static title translated', (string) $result[0]);
// Test the dynamic '_title_callback' route option. // Test the dynamic '_title_callback' route option.
$this->drupalGet('test-page-dynamic-title'); $this->drupalGet('test-page-dynamic-title');
$this->assertTitle('Dynamic title | Drupal'); $this->assertTitle('Dynamic title | Drupal');
$result = $this->xpath('//h1'); $result = $this->xpath('//h1[@class="page-title"]');
$this->assertEqual('Dynamic title', (string) $result[0]); $this->assertEqual('Dynamic title', (string) $result[0]);
// Ensure that titles are cacheable and are escaped normally if the // Ensure that titles are cacheable and are escaped normally if the

View File

@ -67,10 +67,13 @@ class SiteMaintenanceTest extends WebTestBase {
// Logout and verify that offline message is displayed. // Logout and verify that offline message is displayed.
$this->drupalLogout(); $this->drupalLogout();
$this->drupalGet(''); $this->drupalGet('');
$this->assertEqual('Site under maintenance', $this->cssSelect('main h1')[0]);
$this->assertText($offline_message); $this->assertText($offline_message);
$this->drupalGet('node'); $this->drupalGet('node');
$this->assertEqual('Site under maintenance', $this->cssSelect('main h1')[0]);
$this->assertText($offline_message); $this->assertText($offline_message);
$this->drupalGet('user/register'); $this->drupalGet('user/register');
$this->assertEqual('Site under maintenance', $this->cssSelect('main h1')[0]);
$this->assertText($offline_message); $this->assertText($offline_message);
// Verify that user is able to log in. // Verify that user is able to log in.
@ -103,6 +106,7 @@ class SiteMaintenanceTest extends WebTestBase {
// Logout and verify that custom site offline message is displayed. // Logout and verify that custom site offline message is displayed.
$this->drupalLogout(); $this->drupalLogout();
$this->drupalGet(''); $this->drupalGet('');
$this->assertEqual('Site under maintenance', $this->cssSelect('main h1')[0]);
$this->assertRaw($offline_message, 'Found the site offline message.'); $this->assertRaw($offline_message, 'Found the site offline message.');
// Verify that custom site offline message is not displayed on user/password. // Verify that custom site offline message is not displayed on user/password.
@ -121,5 +125,14 @@ class SiteMaintenanceTest extends WebTestBase {
// Log in with temporary login link. // Log in with temporary login link.
$this->drupalPostForm($path, array(), t('Log in')); $this->drupalPostForm($path, array(), t('Log in'));
$this->assertText($user_message); $this->assertText($user_message);
// Regression test to check if title displays in Bartik on maintenance page.
\Drupal::service('theme_handler')->install(array('bartik'));
\Drupal::service('theme_handler')->setDefault('bartik');
// Logout and verify that offline message is displayed in Bartik.
$this->drupalLogout();
$this->drupalGet('');
$this->assertEqual('Site under maintenance', $this->cssSelect('main h1')[0]);
} }
} }

View File

@ -0,0 +1,82 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Update\PageTitleConvertedIntoBlockUpdateTest.
*/
namespace Drupal\system\Tests\Update;
use Drupal\node\Entity\Node;
/**
* Tests the upgrade path for page title being converted into a block.
*
* @see https://www.drupal.org/node/2476947
*
* @group system
*/
class PageTitleConvertedIntoBlockUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.page-title-into-block-2476947.php',
];
}
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// @todo Remove in https://www.drupal.org/node/2568069.
/** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler */
$theme_handler = \Drupal::service('theme_handler');
$theme_handler->refreshInfo();
}
/**
* Tests that page title is being converted into a block.
*/
public function testUpdateHookN() {
$this->runUpdates();
/** @var \Drupal\block\BlockInterface $block_storage */
$block_storage = \Drupal::entityManager()->getStorage('block');
$this->assertRaw('Because your site has custom theme(s) installed, we have placed the page title block in the content region. Please manually review the block configuration and remove the page title variables from your page templates.');
// Disable maintenance mode.
// @todo Can be removed once maintenance mode is automatically turned off
// after updates in https://www.drupal.org/node/2435135.
\Drupal::state()->set('system.maintenance_mode', FALSE);
// We finished updating so we can login the user now.
$this->drupalLogin($this->rootUser);
$page = Node::create([
'type' => 'page',
'title' => 'Page node',
]);
$page->save();
// Page title is visible on the home page.
$this->drupalGet('/node');
$this->assertRaw('page-title');
// Page title is visible on a node page.
$this->drupalGet('node/' . $page->id());
$this->assertRaw('page-title');
$this->drupalGet('admin/structure/block/list/bartik');
/** @var \Drupal\Core\Config\StorageInterface $config_storage */
$config_storage = \Drupal::service('config.storage');
$this->assertTrue($config_storage->exists('block.block.test_theme_page_title'), 'Page title block has been created for the custom theme.');
}
}

View File

@ -1470,16 +1470,16 @@ function system_update_8005() {
default: default:
$custom_themes_installed = TRUE; $custom_themes_installed = TRUE;
$name = sprintf('block.block.%s_local_actions', $theme_name); $name = 'block.block.' . $theme_name . '_local_actions';
$values = [ $values = [
'id' => sprintf('%s_local_actions', $theme_name), 'id' => $theme_name . '_local_actions',
'weight' => -10, 'weight' => -10,
] + $local_actions_default_settings; ] + $local_actions_default_settings;
_system_update_create_block($name, $theme_name, $values); _system_update_create_block($name, $theme_name, $values);
$name = sprintf('block.block.%s_local_tasks', $theme_name); $name = sprintf('block.block.%s_local_tasks', $theme_name);
$values = [ $values = [
'id' => sprintf('%s_local_tasks', $theme_name), 'id' => $theme_name . '_local_tasks',
'weight' => -20, 'weight' => -20,
] + $tabs_default_settings; ] + $tabs_default_settings;
_system_update_create_block($name, $theme_name, $values); _system_update_create_block($name, $theme_name, $values);
@ -1701,6 +1701,89 @@ function system_update_8009() {
} }
} }
/**
* Place page title blocks in every theme.
*/
function system_update_8009() {
// When block module is not installed, there is nothing that could be done
// except showing a warning.
if (!\Drupal::moduleHandler()->moduleExists('block')) {
return t('Block module is not enabled. The page title has been converted to a block, but default page title markup will still display at the top of the main content area.');
}
/** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler */
$theme_handler = \Drupal::service('theme_handler');
$custom_themes_installed = FALSE;
$message = NULL;
$langcode = \Drupal::service('language_manager')->getCurrentLanguage()->getId();
$page_title_default_settings = [
'plugin' => 'page_title_block',
'region' => 'content',
'settings.label' => 'Page title',
'settings.label_display' => 0,
'visibility' => [],
'weight' => -50,
'langcode' => $langcode,
];
foreach ($theme_handler->listInfo() as $theme) {
$theme_name = $theme->getName();
switch ($theme_name) {
case 'bartik':
$name = 'block.block.bartik_page_title';
$values = [
'id' => 'bartik_page_title',
] + $page_title_default_settings;
_system_update_create_block($name, $theme_name, $values);
break;
case 'stark':
$name = 'block.block.stark_page_title';
$values = [
'id' => 'stark_page_title',
'region' => 'content',
] + $page_title_default_settings;
_system_update_create_block($name, $theme_name, $values);
break;
case 'seven':
$name = 'block.block.seven_page_title';
$values = [
'id' => 'seven_page_title',
'region' => 'header',
] + $page_title_default_settings;
_system_update_create_block($name, $theme_name, $values);
break;
case 'classy':
$name = 'block.block.classy_page_title';
$values = [
'id' => 'classy_page_title',
'region' => 'content',
] + $page_title_default_settings;
_system_update_create_block($name, $theme_name, $values);
break;
default:
$custom_themes_installed = TRUE;
$name = sprintf('block.block.%s_page_title', $theme_name);
$values = [
'id' => sprintf('%s_page_title', $theme_name),
'region' => 'content',
'weight' => '-50',
] + $page_title_default_settings;
_system_update_create_block($name, $theme_name, $values);
break;
}
}
if ($custom_themes_installed) {
$message = t('Because your site has custom theme(s) installed, we have placed the page title block in the content region. Please manually review the block configuration and remove the page title variables from your page templates.');
}
return $message;
}
/** /**
* @} End of "addtogroup updates-8.0.0-beta". * @} End of "addtogroup updates-8.0.0-beta".
*/ */

View File

@ -0,0 +1,23 @@
{#
/**
* @file
* Default theme implementation for page titles.
*
* Available variables:
* - title_attributes: HTML attributes for the page title element.
* - title_prefix: Additional output populated by modules, intended to be
* displayed in front of the main title tag that appears in the template.
* - title: The page title, for use in the actual content.
* - title_suffix: Additional output populated by modules, intended to be
* displayed after the main title tag that appears in the template.
*
* @see template_preprocess_page_title()
*
* @ingroup themeable
*/
#}
{{ title_prefix }}
{% if title %}
<h1{{ title_attributes }}>{{ title }}</h1>
{% endif %}
{{ title_suffix }}

View File

@ -26,11 +26,6 @@
* slogan has been disabled in theme settings. * slogan has been disabled in theme settings.
* *
* Page content (in order of occurrence in the default page.html.twig): * Page content (in order of occurrence in the default page.html.twig):
* - title_prefix: Additional output populated by modules, intended to be
* displayed in front of the main title tag that appears in the template.
* - title: The page title, for use in the actual content.
* - title_suffix: Additional output populated by modules, intended to be
* displayed after the main title tag that appears in the template.
* - messages: Status and error messages. Should be displayed prominently. * - messages: Status and error messages. Should be displayed prominently.
* - node: Fully loaded node, if there is an automatically-loaded node * - node: Fully loaded node, if there is an automatically-loaded node
* associated with the page and the node ID is the second argument in the * associated with the page and the node ID is the second argument in the
@ -74,12 +69,6 @@
<a id="main-content" tabindex="-1"></a>{# link is in html.html.twig #} <a id="main-content" tabindex="-1"></a>{# link is in html.html.twig #}
<div class="layout-content"> <div class="layout-content">
{{ title_prefix }}
{% if title %}
<h1>{{ title }}</h1>
{% endif %}
{{ title_suffix }}
{{ page.content }} {{ page.content }}
</div>{# /.layout-content #} </div>{# /.layout-content #}

View File

@ -0,0 +1,17 @@
langcode: en
status: true
dependencies:
theme:
- bartik
id: bartik_page_title
theme: bartik
region: content
weight: -50
provider: null
plugin: page_title_block
settings:
id: page_title_block
label: 'Page title'
provider: core
label_display: '0'
visibility: { }

View File

@ -0,0 +1,60 @@
<?php
/**
* @file
* Contains database additions to drupal-8.bare.standard.php.gz for testing the
* upgrade path of https://www.drupal.org/node/2476947.
*/
use Drupal\Core\Database\Database;
$connection = Database::getConnection();
// Structure of a custom block with visibility settings.
$block_configs[] = \Drupal\Component\Serialization\Yaml::decode(file_get_contents(__DIR__ . '/block.block.testfor2476947.yml'));
foreach ($block_configs as $block_config) {
$connection->insert('config')
->fields([
'collection',
'name',
'data',
])
->values([
'collection' => '',
'name' => 'block.block.' . $block_config['id'],
'data' => serialize($block_config),
])
->execute();
}
// Update the config entity query "index".
$existing_blocks = $connection->select('key_value')
->fields('key_value', ['value'])
->condition('collection', 'config.entity.key_store.block')
->condition('name', 'theme:bartik')
->execute()
->fetchField();
$existing_blocks = unserialize($existing_blocks);
$connection->update('key_value')
->fields([
'value' => serialize(array_merge($existing_blocks, ['block.block.bartik_page_title']))
])
->condition('collection', 'config.entity.key_store.block')
->condition('name', 'theme:bartik')
->execute();
// Enable test theme.
$extensions = $connection->select('config')
->fields('config', ['data'])
->condition('name', 'core.extension')
->execute()
->fetchField();
$extensions = unserialize($extensions);
$connection->update('config')
->fields([
'data' => serialize(array_merge_recursive($extensions, ['theme' => ['test_theme' => 0]]))
])
->condition('name', 'core.extension')
->execute();

View File

@ -29,6 +29,13 @@ class TestDisplayVariant extends VariantBase implements PageVariantInterface, Co
*/ */
protected $mainContent = []; protected $mainContent = [];
/**
* The page title: a string (plain title) or a render array (formatted title).
*
* @var string|array
*/
protected $title = '';
/** /**
* An array of collected contexts. * An array of collected contexts.
* *
@ -69,6 +76,14 @@ class TestDisplayVariant extends VariantBase implements PageVariantInterface, Co
return $this; return $this;
} }
/**
* {@inheritdoc}
*/
public function setTitle($title) {
$this->title = $title;
return $this;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */

View File

@ -50,6 +50,7 @@ class TermTest extends TaxonomyTestBase {
$this->drupalPlaceBlock('local_actions_block'); $this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('local_tasks_block'); $this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy', 'bypass node access'])); $this->drupalLogin($this->drupalCreateUser(['administer taxonomy', 'bypass node access']));
$this->vocabulary = $this->createVocabulary(); $this->vocabulary = $this->createVocabulary();

View File

@ -14,6 +14,12 @@ namespace Drupal\taxonomy\Tests;
*/ */
class VocabularyPermissionsTest extends TaxonomyTestBase { class VocabularyPermissionsTest extends TaxonomyTestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/** /**
* Create, edit and delete a taxonomy term via the user interface. * Create, edit and delete a taxonomy term via the user interface.
*/ */

View File

@ -30,6 +30,7 @@ class VocabularyUiTest extends TaxonomyTestBase {
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy'])); $this->drupalLogin($this->drupalCreateUser(['administer taxonomy']));
$this->vocabulary = $this->createVocabulary(); $this->vocabulary = $this->createVocabulary();
$this->drupalPlaceBlock('local_actions_block'); $this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('page_title_block');
} }
/** /**

View File

@ -1,28 +0,0 @@
/**
* @file
* Javascript related to contextual links.
*/
(function ($) {
"use strict";
/**
* Attaches contextual region classes to views elements.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Adds class `contextual-region` to views elements.
*/
Drupal.behaviors.viewsContextualLinks = {
attach: function (context) {
var id = $('body').attr('data-views-page-contextual-id');
$('[data-contextual-id="' + id + '"]')
.closest(':has(.view)')
.addClass('contextual-region');
}
};
})(jQuery);

View File

@ -60,6 +60,8 @@ class ViewPageController {
$build = $class::buildBasicRenderable($view_id, $display_id, $args, $route); $build = $class::buildBasicRenderable($view_id, $display_id, $args, $route);
Page::setPageRenderArray($build); Page::setPageRenderArray($build);
views_add_contextual_links($build, 'page', $display_id, $build);
return $build; return $build;
} }
} }

View File

@ -47,6 +47,8 @@ class DefaultViewsTest extends ViewTestBase {
protected function setUp() { protected function setUp() {
parent::setUp(); parent::setUp();
$this->drupalPlaceBlock('page_title_block');
// Create Basic page node type. // Create Basic page node type.
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));

View File

@ -34,6 +34,8 @@ class DisabledDisplayTest extends PluginTestBase {
$this->enableViewsTestModule(); $this->enableViewsTestModule();
$this->drupalPlaceBlock('page_title_block');
$admin_user = $this->drupalCreateUser(array('administer site configuration')); $admin_user = $this->drupalCreateUser(array('administer site configuration'));
$this->drupalLogin($admin_user); $this->drupalLogin($admin_user);
} }
@ -58,7 +60,7 @@ class DisabledDisplayTest extends PluginTestBase {
// Enabled page display should return content. // Enabled page display should return content.
$this->drupalGet('test-disabled-display'); $this->drupalGet('test-disabled-display');
$result = $this->xpath('//h1'); $result = $this->xpath('//h1[@class="page-title"]');
$this->assertEqual($result[0], 'test_disabled_display', 'The enabled page_1 display is accessible.'); $this->assertEqual($result[0], 'test_disabled_display', 'The enabled page_1 display is accessible.');
// Disabled page view should 404. // Disabled page view should 404.
@ -77,7 +79,7 @@ class DisabledDisplayTest extends PluginTestBase {
// Check that the originally disabled page_2 display is now enabled. // Check that the originally disabled page_2 display is now enabled.
$this->drupalGet('test-disabled-display-2'); $this->drupalGet('test-disabled-display-2');
$result = $this->xpath('//h1'); $result = $this->xpath('//h1[@class="page-title"]');
$this->assertEqual($result[0], 'test_disabled_display', 'The enabled page_2 display is accessible.'); $this->assertEqual($result[0], 'test_disabled_display', 'The enabled page_2 display is accessible.');
// Disable each disabled display and save the view. // Disable each disabled display and save the view.

View File

@ -19,6 +19,12 @@ use Drupal\views\Views;
*/ */
class BasicTest extends WizardTestBase { class BasicTest extends WizardTestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
function testViewsWizardAndListing() { function testViewsWizardAndListing() {
$this->drupalCreateContentType(array('type' => 'article')); $this->drupalCreateContentType(array('type' => 'article'));
$this->drupalCreateContentType(array('type' => 'page')); $this->drupalCreateContentType(array('type' => 'page'));

View File

@ -15,6 +15,12 @@ namespace Drupal\views\Tests\Wizard;
*/ */
class ItemsPerPageTest extends WizardTestBase { class ItemsPerPageTest extends WizardTestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/** /**
* Tests the number of items per page. * Tests the number of items per page.
*/ */

View File

@ -14,6 +14,12 @@ namespace Drupal\views\Tests\Wizard;
*/ */
class SortingTest extends WizardTestBase { class SortingTest extends WizardTestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/** /**
* Tests the sorting functionality. * Tests the sorting functionality.
*/ */

View File

@ -206,7 +206,8 @@ class ViewsBlockTest extends UnitTestCase {
} }
namespace { namespace {
// @todo replace views_add_contextual_links() // @todo https://www.drupal.org/node/2571679 replace
// views_add_contextual_links().
if (!function_exists('views_add_contextual_links')) { if (!function_exists('views_add_contextual_links')) {
function views_add_contextual_links() { function views_add_contextual_links() {
} }

View File

@ -5,7 +5,7 @@
* Contains \Drupal\Tests\views\Unit\Routing\ViewPageControllerTest. * Contains \Drupal\Tests\views\Unit\Routing\ViewPageControllerTest.
*/ */
namespace Drupal\Tests\views\Unit\Routing; namespace Drupal\Tests\views\Unit\Routing {
use Drupal\Core\Routing\RouteMatch; use Drupal\Core\Routing\RouteMatch;
use Drupal\Tests\UnitTestCase; use Drupal\Tests\UnitTestCase;
@ -181,3 +181,14 @@ class ViewPageControllerTest extends UnitTestCase {
} }
} }
}
namespace {
// @todo https://www.drupal.org/node/2571679 replace
// views_add_contextual_links()
if (!function_exists('views_add_contextual_links')) {
function views_add_contextual_links() {
}
}
}

View File

@ -16,12 +16,3 @@ views.ajax:
- core/jquery.once - core/jquery.once
- core/jquery.form - core/jquery.form
- core/drupal.ajax - core/drupal.ajax
views.contextual-links:
version: VERSION
js:
# Ensure to run before contextual/drupal.contextual-links.
js/views-contextual.js: { weight: -10 }
dependencies:
- core/jquery
- core/drupal

View File

@ -301,39 +301,6 @@ function views_theme_suggestions_container_alter(array &$suggestions, array $var
} }
} }
/**
* Implements MODULE_preprocess_HOOK().
*/
function views_preprocess_html(&$variables) {
if (!\Drupal::moduleHandler()->moduleExists('contextual')) {
return;
}
// If the main content of this page contains a view, attach its contextual
// links to the overall page array. This allows them to be rendered directly
// next to the page title.
if ($render_array = Page::getPageRenderArray()) {
views_add_contextual_links($variables['page'], 'page', $render_array['#display_id'], $render_array);
}
// If the page contains a view as its main content, contextual links may have
// been attached to the page as a whole; for example, by
// views_page_display_pre_render().
// This allows them to be associated with the page and rendered by default
// next to the page title (which we want). However, it also causes the
// Contextual Links module to treat the wrapper for the entire page (i.e.,
// the <body> tag) as the HTML element that these contextual links are
// associated with. This we don't want; for better visual highlighting, we
// prefer a smaller region to be chosen. The region we prefer differs from
// theme to theme and depends on the details of the theme's markup in
// page.html.twig, so we can only find it using JavaScript. We therefore
// remove the "contextual-region" class from the <body> tag here and add
// JavaScript that will insert it back in the correct place.
if (!empty($variables['page']['#views_contextual_links'])) {
$variables['attributes']['data-views-page-contextual-id'] = _contextual_links_to_id($variables['page']['#contextual_links']);
}
}
/** /**
* Adds contextual links associated with a view display to a renderable array. * Adds contextual links associated with a view display to a renderable array.
* *
@ -470,9 +437,6 @@ function views_add_contextual_links(&$render_element, $location, $display_id, ar
// user that may use contextual links, attach Views' contextual links // user that may use contextual links, attach Views' contextual links
// JavaScript. // JavaScript.
$render_element['#cache']['contexts'][] = 'user.permissions'; $render_element['#cache']['contexts'][] = 'user.permissions';
if ($location === 'page' && $render_element['#type'] === 'page' && \Drupal::currentUser()->hasPermission('access contextual links')) {
$render_element['#attached']['library'][] = 'views/views.contextual-links';
}
} }
} }
} }

View File

@ -38,6 +38,18 @@ function template_preprocess_views_view(&$variables) {
$variables['attributes']['class'][] = $variables['css_class']; $variables['attributes']['class'][] = $variables['css_class'];
} }
// contextual_preprocess() only works on render elements, and since this theme
// hook is not for a render element, contextual_preprocess() falls back to the
// first argument and checks if that is a render element. The first element is
// view_array. However, view_array does not get set anywhere, but since we do
// have access to the View object, we can also access the View object's
// element, which is a render element that does have #contextual_links set if
// the display supports it. Doing this allows contextual_preprocess() to
// access this theme hook's render element, and therefore allows this template
// to have contextual links.
// @see views_theme()
$variables['view_array'] = $variables['view']->element;
// Attachments are always updated with the outer view, never by themselves, // Attachments are always updated with the outer view, never by themselves,
// so they do not have dom ids. // so they do not have dom ids.
if (empty($view->is_attachment)) { if (empty($view->is_attachment)) {

View File

@ -25,6 +25,13 @@ class DefaultViewsTest extends UITestBase {
*/ */
public static $testViews = array('test_view_status', 'test_page_display_menu', 'test_page_display_arguments'); public static $testViews = array('test_view_status', 'test_page_display_menu', 'test_page_display_arguments');
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/** /**
* Tests default views. * Tests default views.
*/ */

View File

@ -17,6 +17,12 @@ use Drupal\menu_link_content\Entity\MenuLinkContent;
*/ */
class DisplayPathTest extends UITestBase { class DisplayPathTest extends UITestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */

View File

@ -180,6 +180,8 @@ class DisplayTest extends UITestBase {
$view->enable()->save(); $view->enable()->save();
$this->container->get('router.builder')->rebuildIfNeeded(); $this->container->get('router.builder')->rebuildIfNeeded();
// When no "main content" block is placed, we find a contextual link
// placeholder for editing just the view.
$this->drupalGet('test-display'); $this->drupalGet('test-display');
$id = 'entity.view.edit_form:view=test_display:location=page&name=test_display&display_id=page_1&langcode=en'; $id = 'entity.view.edit_form:view=test_display:location=page&name=test_display&display_id=page_1&langcode=en';
// @see \Drupal\contextual\Tests\ContextualDynamicContextTest:assertContextualLinkPlaceHolder() // @see \Drupal\contextual\Tests\ContextualDynamicContextTest:assertContextualLinkPlaceHolder()
@ -192,6 +194,15 @@ class DisplayTest extends UITestBase {
$this->assertResponse(200); $this->assertResponse(200);
$json = Json::decode($response); $json = Json::decode($response);
$this->assertIdentical($json[$id], '<ul class="contextual-links"><li class="entityviewedit-form"><a href="' . base_path() . 'admin/structure/views/view/test_display/edit/page_1">Edit view</a></li></ul>'); $this->assertIdentical($json[$id], '<ul class="contextual-links"><li class="entityviewedit-form"><a href="' . base_path() . 'admin/structure/views/view/test_display/edit/page_1">Edit view</a></li></ul>');
// When a "main content" is placed, we still find a contextual link
// placeholder for editing just the view (not the main content block).
// @see system_block_view_system_main_block_alter()
$this->drupalPlaceBlock('system_main_block', ['id' => 'main_content']);
$this->drupalGet('test-display');
$id = 'entity.view.edit_form:view=test_display:location=page&name=test_display&display_id=page_1&langcode=en';
// @see \Drupal\contextual\Tests\ContextualDynamicContextTest:assertContextualLinkPlaceHolder()
$this->assertRaw('<div' . new Attribute(array('data-contextual-id' => $id)) . '></div>', format_string('Contextual link placeholder with id @id exists.', array('@id' => $id)));
} }
/** /**

View File

@ -14,6 +14,12 @@ namespace Drupal\views_ui\Tests;
*/ */
class DuplicateTest extends UITestBase { class DuplicateTest extends UITestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/** /**
* Checks if duplicated view exists and has correct label. * Checks if duplicated view exists and has correct label.
*/ */

View File

@ -27,6 +27,15 @@ class HandlerTest extends UITestBase {
*/ */
public static $testViews = array('test_view_empty', 'test_view_broken', 'node'); public static $testViews = array('test_view_empty', 'test_view_broken', 'node');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/** /**
* Overrides \Drupal\views\Tests\ViewTestBase::schemaDefinition(). * Overrides \Drupal\views\Tests\ViewTestBase::schemaDefinition().
* *
@ -191,7 +200,7 @@ class HandlerTest extends UITestBase {
$this->assertIdentical((string) $result[0], $text, 'Ensure the broken handler text was found.'); $this->assertIdentical((string) $result[0], $text, 'Ensure the broken handler text was found.');
$this->drupalGet($href); $this->drupalGet($href);
$result = $this->xpath('//h1'); $result = $this->xpath('//h1[@class="page-title"]');
$this->assertTrue(strpos((string) $result[0], $text) !== FALSE, 'Ensure the broken handler text was found.'); $this->assertTrue(strpos((string) $result[0], $text) !== FALSE, 'Ensure the broken handler text was found.');
$original_configuration = [ $original_configuration = [

View File

@ -14,6 +14,12 @@ namespace Drupal\views_ui\Tests;
*/ */
class OverrideDisplaysTest extends UITestBase { class OverrideDisplaysTest extends UITestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/** /**
* Tests that displays can be overridden via the UI. * Tests that displays can be overridden via the UI.
*/ */

View File

@ -0,0 +1,17 @@
langcode: en
status: true
dependencies:
theme:
- stark
id: stark_page_title
theme: stark
region: content
weight: -30
provider: null
plugin: page_title_block
settings:
id: page_title_block
label: 'Page title'
provider: core
label_display: '0'
visibility: { }

View File

@ -0,0 +1,17 @@
langcode: en
status: true
dependencies:
theme:
- bartik
id: bartik_page_title
theme: bartik
region: content
weight: -50
provider: null
plugin: page_title_block
settings:
id: page_title_block
label: 'Page title'
provider: core
label_display: '0'
visibility: { }

View File

@ -0,0 +1,17 @@
langcode: en
status: true
dependencies:
theme:
- classy
id: classy_page_title
theme: classy
region: content
weight: -50
provider: null
plugin: page_title_block
settings:
id: page_title_block
label: 'Page title'
provider: core
label_display: '0'
visibility: { }

View File

@ -0,0 +1,17 @@
langcode: en
status: true
dependencies:
theme:
- seven
id: seven_page_title
theme: seven
region: header
weight: -30
provider: null
plugin: page_title_block
settings:
id: page_title_block
label: 'Page title'
provider: core
label_display: '0'
visibility: { }

View File

@ -39,7 +39,7 @@ function bartik_preprocess_html(&$variables) {
/** /**
* Implements hook_preprocess_HOOK() for page templates. * Implements hook_preprocess_HOOK() for page templates.
*/ */
function bartik_preprocess_page(&$variables) { function bartik_preprocess_page_title(&$variables) {
// Since the title and the shortcut link are both block level elements, // Since the title and the shortcut link are both block level elements,
// positioning them next to each other is much simpler with a wrapper div. // positioning them next to each other is much simpler with a wrapper div.
if (!empty($variables['title_suffix']['add_or_remove_shortcut']) && $variables['title']) { if (!empty($variables['title_suffix']['add_or_remove_shortcut']) && $variables['title']) {

View File

@ -0,0 +1,16 @@
{% extends "@classy/content/page-title.html.twig" %}
{#
/**
* @file
* Bartik's theme implementation for a page title.
*
* Available variables:
* - title_attributes: HTML attributes for the page title element.
* - title_prefix: Additional output populated by modules, intended to be
* displayed in front of the main title tag that appears in the template.
* - title: The page title, for use in the actual content.
* - title_suffix: Additional output populated by modules, intended to be
* displayed after the main title tag that appears in the template.
*/
#}
{% set title_attributes = title_attributes.addClass('title') %}

View File

@ -27,11 +27,6 @@
* slogan has been disabled in theme settings. * slogan has been disabled in theme settings.
* Page content (in order of occurrence in the default page.html.twig): * Page content (in order of occurrence in the default page.html.twig):
* - title_prefix: Additional output populated by modules, intended to be
* displayed in front of the main title tag that appears in the template.
* - title: The page title, for use in the actual content.
* - title_suffix: Additional output populated by modules, intended to be
* displayed after the main title tag that appears in the template.
* - node: Fully loaded node, if there is an automatically-loaded node * - node: Fully loaded node, if there is an automatically-loaded node
* associated with the page and the node ID is the second argument in the * associated with the page and the node ID is the second argument in the
* page's path (e.g. node/12345 and node/12345/revisions, but not * page's path (e.g. node/12345 and node/12345/revisions, but not
@ -90,13 +85,6 @@
<main id="content" class="column main-content js-quickedit-main-content" role="main"> <main id="content" class="column main-content js-quickedit-main-content" role="main">
<section class="section"> <section class="section">
<a id="main-content" tabindex="-1"></a> <a id="main-content" tabindex="-1"></a>
{{ title_prefix }}
{% if title %}
<h1 class="title page-title">
{{ title }}
</h1>
{% endif %}
{{ title_suffix }}
{{ page.content }} {{ page.content }}
</section> </section>
</main> </main>

View File

@ -0,0 +1,21 @@
{#
/**
* @file
* Theme override for page titles.
*
* Available variables:
* - title_attributes: HTML attributes for the page title element.
* - title_prefix: Additional output populated by modules, intended to be
* displayed in front of the main title tag that appears in the template.
* - title: The page title, for use in the actual content.
* - title_suffix: Additional output populated by modules, intended to be
* displayed after the main title tag that appears in the template.
*
* @see template_preprocess_page_title()
*/
#}
{{ title_prefix }}
{% if title %}
<h1{{ title_attributes.addClass('page-title') }}>{{ title }}</h1>
{% endif %}
{{ title_suffix }}

View File

@ -26,11 +26,6 @@
* slogan has been disabled in theme settings. * slogan has been disabled in theme settings.
* *
* Page content (in order of occurrence in the default page.html.twig): * Page content (in order of occurrence in the default page.html.twig):
* - title_prefix: Additional output populated by modules, intended to be
* displayed in front of the main title tag that appears in the template.
* - title: The page title, for use in the actual content.
* - title_suffix: Additional output populated by modules, intended to be
* displayed after the main title tag that appears in the template.
* - node: Fully loaded node, if there is an automatically-loaded node * - node: Fully loaded node, if there is an automatically-loaded node
* associated with the page and the node ID is the second argument in the * associated with the page and the node ID is the second argument in the
* page's path (e.g. node/12345 and node/12345/revisions, but not * page's path (e.g. node/12345 and node/12345/revisions, but not
@ -72,12 +67,6 @@
<a id="main-content" tabindex="-1"></a>{# link is in html.html.twig #} <a id="main-content" tabindex="-1"></a>{# link is in html.html.twig #}
<div class="layout-content"> <div class="layout-content">
{{ title_prefix }}
{% if title %}
<h1>{{ title }}</h1>
{% endif %}
{{ title_suffix }}
{{ page.content }} {{ page.content }}
</div>{# /.layout-content #} </div>{# /.layout-content #}

View File

@ -27,11 +27,6 @@
* slogan has been disabled in theme settings. * slogan has been disabled in theme settings.
* *
* Page content (in order of occurrence in the default page.html.twig): * Page content (in order of occurrence in the default page.html.twig):
* - title_prefix: Additional output populated by modules, intended to be
* displayed in front of the main title tag that appears in the template.
* - title: The page title, for use in the actual content.
* - title_suffix: Additional output populated by modules, intended to be
* displayed after the main title tag that appears in the template.
* - node: Fully loaded node, if there is an automatically-loaded node * - node: Fully loaded node, if there is an automatically-loaded node
* associated with the page and the node ID is the second argument in the * associated with the page and the node ID is the second argument in the
* page's path (e.g. node/12345 and node/12345/revisions, but not * page's path (e.g. node/12345 and node/12345/revisions, but not
@ -52,11 +47,6 @@
#} #}
<header class="content-header clearfix"> <header class="content-header clearfix">
<div class="layout-container"> <div class="layout-container">
{{ title_prefix }}
{% if title %}
<h1 class="page-title">{{ title }}</h1>
{% endif %}
{{ title_suffix }}
{{ page.header }} {{ page.header }}
</div> </div>
</header> </header>