Commit bef7274a authored by catch's avatar catch

Issue #2476947 by mdrummond, Wim Leers, davidhernandez, lauriii, joelpittet,...

Issue #2476947 by mdrummond, Wim Leers, davidhernandez, lauriii, joelpittet, andypost, Cottser, JeroenT, Manuel Garcia, rpayanm: Convert "title" page element into a block
parent f8e3bf23
......@@ -1342,9 +1342,6 @@ function template_preprocess_html(&$variables) {
function template_preprocess_page(&$variables) {
$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) {
if (!isset($variables['page'][$region])) {
$variables['page'][$region] = array();
......@@ -1465,6 +1462,10 @@ function template_preprocess_maintenance_page(&$variables) {
$variables['logo'] = theme_get_setting('logo.url');
$variables['site_name'] = $site_config->get('name');
$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(
'render element' => 'page',
),
'page_title' => array(
'variables' => array('title' => NULL),
),
'region' => array(
'render element' => 'elements',
),
......
......@@ -10,7 +10,7 @@
/**
* 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
*/
......
<?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,
];
}
}
<?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);
}
......@@ -36,4 +36,15 @@ interface PageVariantInterface extends VariantInterface {
*/
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);
}
<?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,
];
}
}
......@@ -193,11 +193,18 @@ public function renderResponse(array $main_content, Request $request, RouteMatch
* If the selected display variant does not implement PageVariantInterface.
*/
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,
// 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;
$title = $get_title($page);
}
// 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.
......@@ -229,6 +236,8 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte
];
}
$title = $get_title($main_content);
// Instantiate the page display, and give it the main content.
$page_display = $this->displayVariantManager->createInstance($variant_id);
if (!$page_display instanceof PageVariantInterface) {
......@@ -236,6 +245,7 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte
}
$page_display
->setMainContent($main_content)
->setTitle($title)
->addCacheableDependency($event)
->setConfiguration($event->getPluginConfiguration());
// Some display variants need to be passed an array of contexts with
......@@ -268,10 +278,6 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte
// Allow hooks to add attachments to $page['#attached'].
$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];
}
......
......@@ -27,6 +27,13 @@ class SimplePageVariant extends VariantBase implements PageVariantInterface {
*/
protected $mainContent;
/**
* The page title: a string (plain title) or a render array (formatted title).
*
* @var string|array
*/
protected $title = '';
/**
* {@inheritdoc}
*/
......@@ -35,17 +42,30 @@ public function setMainContent(array $main_content) {
return $this;
}
/**
* {@inheritdoc}
*/
public function setTitle($title) {
$this->title = $title;
return $this;
}
/**
* {@inheritdoc}
*/
public function build() {
$build = [
'content' => [
'main_content' => $this->mainContent,
'messages' => [
'#type' => 'status_messages',
'#weight' => -1000,
],
'page_title' => [
'#type' => 'page_title',
'#title' => $this->title,
'#weight' => -900,
],
'main_content' => ['#weight' => -800] + $this->mainContent,
],
];
return $build;
......
......@@ -14,6 +14,13 @@
* @group aggregator
*/
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.
*/
......
......@@ -23,6 +23,12 @@ class AggregatorRenderingTest extends AggregatorTestBase {
*/
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.
*/
......
......@@ -8,6 +8,7 @@
namespace Drupal\block;
use Drupal\Core\Block\MainContentBlockPluginInterface;
use Drupal\Core\Block\TitleBlockPluginInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityManagerInterface;
......@@ -100,12 +101,13 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la
'tags' => $cache_tags,
'max-age' => $plugin->getCacheMaxAge(),
],
'#weight' => $entity->getWeight(),
);
// Allow altering of cacheability metadata or setting #create_placeholder.
$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
// be built lazily.
$build[$entity_id] += static::buildPreRenderableBlock($entity, $this->moduleHandler());
......
......@@ -9,6 +9,7 @@
use Drupal\block\BlockRepositoryInterface;
use Drupal\Core\Block\MainContentBlockPluginInterface;
use Drupal\Core\Block\TitleBlockPluginInterface;
use Drupal\Core\Block\MessagesBlockPluginInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Display\PageVariantInterface;
......@@ -63,6 +64,13 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont
*/
protected $mainContent = [];
/**
* The page title: a string (plain title) or a render array (formatted title).
*
* @var string|array
*/
protected $title = '';
/**
* Constructs a new BlockPageVariant.
*
......@@ -108,6 +116,14 @@ public function setMainContent(array $main_content) {
return $this;
}
/**
* {@inheritdoc}
*/
public function setTitle($title) {
$this->title = $title;
return $this;
}
/**
* {@inheritdoc}
*/
......@@ -131,6 +147,9 @@ public function build() {
$block_plugin->setMainContent($this->mainContent);
$main_content_block_displayed = TRUE;
}
elseif ($block_plugin instanceof TitleBlockPluginInterface) {
$block_plugin->setTitle($this->title);
}
elseif ($block_plugin instanceof MessagesBlockPluginInterface) {
$messages_block_displayed = TRUE;
}
......@@ -138,8 +157,9 @@ public function build() {
// 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,
// with other placed blocks "decorating" it.
if ($block_plugin instanceof MainContentBlockPluginInterface) {
// with other placed blocks "decorating" it. Analogous reasoning for the
// title block.
if ($block_plugin instanceof MainContentBlockPluginInterface || $block_plugin instanceof TitleBlockPluginInterface) {
unset($build[$region][$key]['#cache']['keys']);
}
}
......@@ -165,6 +185,12 @@ public function build() {
];
}
// 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
// render array. This is done to prevent issues with empty regions being
// displayed.
......
......@@ -135,6 +135,9 @@ function testBlockVisibilityListedEmpty() {
* Test configuring and moving a module-define block to specific regions.
*/
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.
$block = array();
$block['id'] = 'system_powered_by_block';
......
......@@ -74,28 +74,32 @@ public function setUpDisplayVariant($configuration = array(), $definition = arra
public function providerBuild() {
$blocks_config = array(
'block1' => array(
// region, is main content block, is messages block
'top', FALSE, FALSE,
// region, is main content block, is messages block, is title block
'top', FALSE, FALSE, FALSE,
),
// Test multiple blocks in the same region.
'block2' => array(
'bottom', FALSE, FALSE,
'bottom', FALSE, FALSE, FALSE,
),
'block3' => array(
'bottom', FALSE, FALSE,
'bottom', FALSE, FALSE, FALSE,
),
// Test a block implementing MainContentBlockPluginInterface.
'block4' => array(
'center', TRUE, FALSE,
'center', TRUE, FALSE, FALSE,
),
// Test a block implementing MessagesBlockPluginInterface.
'block5' => array(
'center', FALSE, TRUE,
'center', FALSE, TRUE, FALSE,
),
// Test a block implementing TitleBlockPluginInterface.
'block6' => array(
'center', FALSE, FALSE, TRUE,
),
);
$test_cases = [];
$test_cases[] = [$blocks_config, 5,
$test_cases[] = [$blocks_config, 6,
[
'#cache' => [
'tags' => [
......@@ -113,6 +117,7 @@ public function providerBuild() {
'center' => [
'block4' => [],
'block5' => [],
'block6' => [],
'#sorted' => TRUE,
],
'bottom' => [
......@@ -123,7 +128,7 @@ public function providerBuild() {
],
];
unset($blocks_config['block5']);
$test_cases[] = [$blocks_config, 4,
$test_cases[] = [$blocks_config, 5,
[
'#cache' => [
'tags' => [
......@@ -139,6 +144,7 @@ public function providerBuild() {
],
'center' => [
'block4' => [],
'block6' => [],
'#sorted' => TRUE,
],
'bottom' => [
......@@ -157,6 +163,7 @@ public function providerBuild() {
],
];
unset($blocks_config['block4']);
unset($blocks_config['block6']);
$test_cases[] = [$blocks_config, 3,
[
'#cache' => [
......@@ -205,6 +212,7 @@ public function testBuild(array $blocks_config, $visible_block_count, array $exp
$block_plugin = $this->getMock('Drupal\Core\Block\BlockPluginInterface');
$main_content_block_plugin = $this->getMock('Drupal\Core\Block\MainContentBlockPluginInterface');
$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) {
$block = $this->getMock('Drupal\block\BlockInterface');
$block->expects($this->any())
......@@ -212,7 +220,7 @@ public function testBuild(array $blocks_config, $visible_block_count, array $exp
->willReturn([]);
$block->expects($this->atLeastOnce())
->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;
}
$this->blockViewBuilder->expects($this->exactly($visible_block_count))
......
......@@ -50,6 +50,8 @@ protected function setUp() {
$this->bundle = 'basic';
$this->testLanguageSelector = FALSE;
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/**
......
......@@ -42,6 +42,12 @@ class BlockContentTypeTest extends BlockContentTestBase {
*/
protected $autoCreateBasicBlockType = FALSE;
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/**
* Tests creating a block type programmatically and via a form.
*/
......
......@@ -17,6 +17,12 @@
*/
class PageEditTest extends BlockContentTestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/**
* Checks block edit functionality.
*/
......
......@@ -68,6 +68,7 @@ class BookTest extends WebTestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block');
$this->drupalPlaceBlock('page_title_block');
// node_access_test requires a node_access_rebuild().
node_access_rebuild();
......
......@@ -15,6 +15,13 @@
* @group comment
*/
class CommentAdminTest extends CommentTestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/**
* Test comment approval functionality through admin/content/comment.
*/
......
......@@ -50,6 +50,7 @@ class CommentNonNodeTest extends WebTestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block');
$this->drupalPlaceBlock('page_title_block');
// Create a bundle for entity_test.
entity_test_create_bundle('entity_test', 'Entity Test', 'entity_test');
......
......@@ -191,14 +191,22 @@ public function postComment($entity, $comment, $subject = '', $contact = NULL, $
*/
function commentExists(CommentInterface $comment = NULL, $reply = FALSE) {
if ($comment) {
$regex = '!' . ($reply ? '<div class="indented">(.*?)' : '');
$regex .= '<a id="comment-' . $comment->id() . '"(.*?)';
$regex .= $comment->getSubject() . '(.*?)';
$regex .= $comment->comment_body->value . '(.*?)';
$regex .= ($reply ? '</article>\s</div>(.*?)' : '');
$regex .= '!s';
return (boolean) preg_match($regex, $this->getRawContent());
$comment_element = $this->cssSelect('.comment-wrapper ' . ($reply ? '.indented ' : '') . '#comment-' . $comment->id() . ' ~ article');
if (empty($comment_element)) {
return FALSE;
}
$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 {
return FALSE;
......
......@@ -43,6 +43,9 @@ class CommentTypeTest extends CommentTestBase {
*/
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
$this->adminUser = $this->drupalCreateUser($this->permissions);
}
......
......@@ -28,6 +28,12 @@ class ConfigSingleImportExportTest extends WebTestBase {
'config_test'
];
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
}
/**
* Tests importing a single configuration file.
*/
......
......@@ -69,6 +69,7 @@ protected function setUp() {
}
$this->localeStorage = $this->container->get('locale.storage');
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
}
/**
......
......@@ -119,6 +119,7 @@ protected function setUp() {
}
$this->localeStorage = $this->container->get('locale.storage');
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
}
/**
......
......@@ -40,6 +40,7 @@ protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block');
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('page_title_block');
}
/**
......
......@@ -49,6 +49,7 @@ class DbLogTest extends WebTestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block');
$this->drupalPlaceBlock('page_title_block');
// Create users with specific permissions.
$this->adminUser = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports', 'administer users'));
......
......@@ -30,6 +30,7 @@ protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('page_title_block');
}
/**
......
......@@ -43,6 +43,7 @@ protected function setUp() {
$this->drupalPlaceBlock('system_breadcrumb_block');
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
// 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'));
......
......@@ -70,6 +70,7 @@ protected function setUp() {
$this->drupalPlaceBlock('system_breadcrumb_block');
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
// 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'));
......
......@@ -71,6 +71,8 @@ class FilterFormatAccessTest extends WebTestBase {
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');